From 785d9c9458e942645ec1604bd266774c7f1c8dd3 Mon Sep 17 00:00:00 2001 From: ggetz Date: Wed, 20 Nov 2024 16:18:34 -0500 Subject: [PATCH 1/4] Throttle total resources used for environment maps --- .../engine/Source/Renderer/ContextLimits.js | 2 +- .../Scene/DynamicEnvironmentMapManager.js | 89 +++++++++++++++++-- 2 files changed, 85 insertions(+), 6 deletions(-) diff --git a/packages/engine/Source/Renderer/ContextLimits.js b/packages/engine/Source/Renderer/ContextLimits.js index b6c5e5731f13..c435971762ac 100644 --- a/packages/engine/Source/Renderer/ContextLimits.js +++ b/packages/engine/Source/Renderer/ContextLimits.js @@ -44,7 +44,7 @@ Object.defineProperties(ContextLimits, { }, /** - * The approximate maximum cube mape width and height supported by this WebGL implementation. + * The approximate maximum cube map width and height supported by this WebGL implementation. * The minimum is 16, but most desktop and laptop implementations will support much larger sizes like 8,192. * @memberof ContextLimits * @type {number} diff --git a/packages/engine/Source/Scene/DynamicEnvironmentMapManager.js b/packages/engine/Source/Scene/DynamicEnvironmentMapManager.js index 40fd2560f1be..66adbe9231b1 100644 --- a/packages/engine/Source/Scene/DynamicEnvironmentMapManager.js +++ b/packages/engine/Source/Scene/DynamicEnvironmentMapManager.js @@ -12,6 +12,7 @@ import PixelFormat from "../Core/PixelFormat.js"; import SceneMode from "./SceneMode.js"; import Transforms from "../Core/Transforms.js"; import ComputeCommand from "../Renderer/ComputeCommand.js"; +import ContextLimits from "../Renderer/ContextLimits.js"; import CubeMap from "../Renderer/CubeMap.js"; import Framebuffer from "../Renderer/Framebuffer.js"; import Texture from "../Renderer/Texture.js"; @@ -79,7 +80,11 @@ function DynamicEnvironmentMapManager(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); - const mipmapLevels = defaultValue(options.mipmapLevels, 10); + const mipmapLevels = Math.min( + defaultValue(options.mipmapLevels, 10), + Math.log2(ContextLimits.maximumCubeMapSize), + ); + this._mipmapLevels = mipmapLevels; this._radianceMapComputeCommands = new Array(6); this._convolutionComputeCommands = new Array((mipmapLevels - 1) * 6); @@ -295,6 +300,62 @@ Object.defineProperties(DynamicEnvironmentMapManager.prototype, { }, }); +console.log(ContextLimits); + +// Internally manage a queue of commands across all instances to prevent too many commands from being added in a single frame and using too much memory at once. +DynamicEnvironmentMapManager._maximumComputeCommandCount = + Math.log2(ContextLimits.maximumCubeMapSize) * 6; // Scale relative to GPU resources available +DynamicEnvironmentMapManager._activeComputeCommandCount = 0; +DynamicEnvironmentMapManager._nextFrameCommandQueue = []; +/** + * Add a command to the queue. If possible, it will be added to the list of commands for the next frame. Otherwise, it will be added to a backlog + * and attempted next frame. + * @private + * @param {ComputeCommand} command The created command + * @param {FrameState} frameState The current frame state + */ +DynamicEnvironmentMapManager._queueCommand = (command, frameState) => { + if ( + DynamicEnvironmentMapManager._activeComputeCommandCount >= + DynamicEnvironmentMapManager._maximumComputeCommandCount + ) { + // Command will instead be scheduled next frame + DynamicEnvironmentMapManager._nextFrameCommandQueue.push(command); + return; + } + + frameState.commandList.push(command); + DynamicEnvironmentMapManager._activeComputeCommandCount++; +}; +/** + * If there are any backlogged commands, queue up as many as possible for the next frame. + * @private + * @param {FrameState} frameState The current frame state + */ +DynamicEnvironmentMapManager._updateCommandQueue = (frameState) => { + if ( + DynamicEnvironmentMapManager._nextFrameCommandQueue.length > 0 && + DynamicEnvironmentMapManager._activeComputeCommandCount < + DynamicEnvironmentMapManager._maximumComputeCommandCount + ) { + let command = DynamicEnvironmentMapManager._nextFrameCommandQueue.pop(); + while ( + defined(command) && + DynamicEnvironmentMapManager._activeComputeCommandCount < + DynamicEnvironmentMapManager._maximumComputeCommandCount + ) { + if (command.canceled) { + command = DynamicEnvironmentMapManager._nextFrameCommandQueue.pop(); + continue; + } + + frameState.commandList.push(command); + DynamicEnvironmentMapManager._activeComputeCommandCount++; + command = DynamicEnvironmentMapManager._nextFrameCommandQueue.pop(); + } + } +}; + /** * Sets the owner for the input DynamicEnvironmentMapManager if there wasn't another owner. * Destroys the owner's previous DynamicEnvironmentMapManager if setting is successful. @@ -334,15 +395,25 @@ DynamicEnvironmentMapManager.setOwner = function ( DynamicEnvironmentMapManager.prototype.reset = function () { let length = this._radianceMapComputeCommands.length; for (let i = 0; i < length; ++i) { + if (defined(this._radianceMapComputeCommands[i])) { + this._radianceMapComputeCommands[i].canceled = true; + DynamicEnvironmentMapManager._activeComputeCommandCount--; + } this._radianceMapComputeCommands[i] = undefined; } length = this._convolutionComputeCommands.length; for (let i = 0; i < length; ++i) { + if (defined(this._convolutionComputeCommands[i])) { + this._convolutionComputeCommands[i].canceled = true; + DynamicEnvironmentMapManager._activeComputeCommandCount--; + } this._convolutionComputeCommands[i] = undefined; } if (defined(this._irradianceComputeCommand)) { + this._irradianceComputeCommand.canceled = true; + DynamicEnvironmentMapManager._activeComputeCommandCount--; this._irradianceComputeCommand = undefined; } @@ -520,14 +591,17 @@ function updateRadianceMap(manager, frameState) { framebuffer._unBind(); framebuffer.destroy(); + DynamicEnvironmentMapManager._activeComputeCommandCount--; + if (!commands.some(defined)) { manager._convolutionsCommandsDirty = true; manager._shouldRegenerateShaders = true; } }, }); - frameState.commandList.push(command); + manager._radianceMapComputeCommands[i] = command; + DynamicEnvironmentMapManager._queueCommand(command, frameState); i++; } manager._radianceCommandsDirty = false; @@ -554,7 +628,7 @@ function updateSpecularMaps(manager, frameState) { const getPostExecute = (index, texture, face, level) => () => { // Copy output texture to corresponding face and mipmap level const commands = manager._convolutionComputeCommands; - if (!defined(commands[index])) { + if (!defined(commands[index]) || commands[index].canceled) { // This command was cancelled return; } @@ -562,6 +636,7 @@ function updateSpecularMaps(manager, frameState) { radianceCubeMap.copyFace(frameState, texture, face, level); facesCopied++; + DynamicEnvironmentMapManager._activeComputeCommandCount--; // All faces and levels have been copied if (facesCopied === manager._specularMapTextures.length) { @@ -619,7 +694,7 @@ function updateSpecularMaps(manager, frameState) { postExecute: getPostExecute(index, texture, face, level), }); manager._convolutionComputeCommands[index] = command; - frameState.commandList.push(command); + DynamicEnvironmentMapManager._queueCommand(command, frameState); ++index; } @@ -674,10 +749,12 @@ function updateIrradianceResources(manager, frameState) { manager._irradianceTextureDirty = false; manager._irradianceComputeCommand = undefined; manager._sphericalHarmonicCoefficientsDirty = true; + + DynamicEnvironmentMapManager._activeComputeCommandCount--; }, }); manager._irradianceComputeCommand = command; - frameState.commandList.push(command); + DynamicEnvironmentMapManager._queueCommand(command, frameState); manager._irradianceTextureDirty = true; } @@ -744,6 +821,8 @@ DynamicEnvironmentMapManager.prototype.update = function (frameState) { return; } + DynamicEnvironmentMapManager._updateCommandQueue(frameState); + const dynamicLighting = frameState.atmosphere.dynamicLighting; const regenerateEnvironmentMap = atmosphereNeedsUpdate(this, frameState) || From e503014a670051b92a403a95e9c92b5fb3b0b427 Mon Sep 17 00:00:00 2001 From: ggetz Date: Wed, 20 Nov 2024 16:43:03 -0500 Subject: [PATCH 2/4] Update CHANGES.md --- CHANGES.md | 1 + .../Source/Scene/DynamicEnvironmentMapManager.js | 11 +++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 6553014c59d6..3b415a3cc96f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,6 +12,7 @@ ##### Fixes :wrench: - Fix label rendering bug in WebGL1 contexts. [#12301](https://github.com/CesiumGS/cesium/pull/12301) +- Fixed lag or crashes when loading many models in the same frame. [#12320](https://github.com/CesiumGS/cesium/pull/12320) #### @cesium/widgets diff --git a/packages/engine/Source/Scene/DynamicEnvironmentMapManager.js b/packages/engine/Source/Scene/DynamicEnvironmentMapManager.js index 66adbe9231b1..5f4545301e09 100644 --- a/packages/engine/Source/Scene/DynamicEnvironmentMapManager.js +++ b/packages/engine/Source/Scene/DynamicEnvironmentMapManager.js @@ -300,11 +300,8 @@ Object.defineProperties(DynamicEnvironmentMapManager.prototype, { }, }); -console.log(ContextLimits); - // Internally manage a queue of commands across all instances to prevent too many commands from being added in a single frame and using too much memory at once. -DynamicEnvironmentMapManager._maximumComputeCommandCount = - Math.log2(ContextLimits.maximumCubeMapSize) * 6; // Scale relative to GPU resources available +DynamicEnvironmentMapManager._maximumComputeCommandCount = 8; // This value is updated once a context is created. DynamicEnvironmentMapManager._activeComputeCommandCount = 0; DynamicEnvironmentMapManager._nextFrameCommandQueue = []; /** @@ -686,7 +683,7 @@ function updateSpecularMaps(manager, frameState) { owner: manager, uniformMap: { u_roughness: () => level / (mipmapLevels - 1), - u_radianceTexture: () => radianceCubeMap, + u_radianceTexture: () => radianceCubeMap ?? context.defaultTexture, u_faceDirection: () => { return CubeMap.getDirection(face, scratchCartesian); }, @@ -739,7 +736,7 @@ function updateIrradianceResources(manager, frameState) { fragmentShaderSource: fs, outputTexture: texture, uniformMap: { - u_radianceMap: () => manager._radianceCubeMap, + u_radianceMap: () => manager._radianceCubeMap ?? context.defaultTexture, }, postExecute: () => { if (!defined(manager._irradianceComputeCommand)) { @@ -821,6 +818,8 @@ DynamicEnvironmentMapManager.prototype.update = function (frameState) { return; } + DynamicEnvironmentMapManager._maximumComputeCommandCount = + Math.log2(ContextLimits.maximumCubeMapSize) * 6; // Scale relative to GPU resources available DynamicEnvironmentMapManager._updateCommandQueue(frameState); const dynamicLighting = frameState.atmosphere.dynamicLighting; From a944f7d4ec6ad6c80e062607165ddd9e707040ac Mon Sep 17 00:00:00 2001 From: ggetz Date: Mon, 25 Nov 2024 12:57:21 -0500 Subject: [PATCH 3/4] Cleanup --- .../Source/Scene/DynamicEnvironmentMapManager.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/engine/Source/Scene/DynamicEnvironmentMapManager.js b/packages/engine/Source/Scene/DynamicEnvironmentMapManager.js index 5f4545301e09..f3897e2ff0c3 100644 --- a/packages/engine/Source/Scene/DynamicEnvironmentMapManager.js +++ b/packages/engine/Source/Scene/DynamicEnvironmentMapManager.js @@ -330,25 +330,29 @@ DynamicEnvironmentMapManager._queueCommand = (command, frameState) => { * @param {FrameState} frameState The current frame state */ DynamicEnvironmentMapManager._updateCommandQueue = (frameState) => { + DynamicEnvironmentMapManager._maximumComputeCommandCount = Math.log2( + ContextLimits.maximumCubeMapSize, + ); // Scale relative to GPU resources available + if ( DynamicEnvironmentMapManager._nextFrameCommandQueue.length > 0 && DynamicEnvironmentMapManager._activeComputeCommandCount < DynamicEnvironmentMapManager._maximumComputeCommandCount ) { - let command = DynamicEnvironmentMapManager._nextFrameCommandQueue.pop(); + let command = DynamicEnvironmentMapManager._nextFrameCommandQueue.shift(); while ( defined(command) && DynamicEnvironmentMapManager._activeComputeCommandCount < DynamicEnvironmentMapManager._maximumComputeCommandCount ) { if (command.canceled) { - command = DynamicEnvironmentMapManager._nextFrameCommandQueue.pop(); + command = DynamicEnvironmentMapManager._nextFrameCommandQueue.shift(); continue; } frameState.commandList.push(command); DynamicEnvironmentMapManager._activeComputeCommandCount++; - command = DynamicEnvironmentMapManager._nextFrameCommandQueue.pop(); + command = DynamicEnvironmentMapManager._nextFrameCommandQueue.shift(); } } }; @@ -818,8 +822,6 @@ DynamicEnvironmentMapManager.prototype.update = function (frameState) { return; } - DynamicEnvironmentMapManager._maximumComputeCommandCount = - Math.log2(ContextLimits.maximumCubeMapSize) * 6; // Scale relative to GPU resources available DynamicEnvironmentMapManager._updateCommandQueue(frameState); const dynamicLighting = frameState.atmosphere.dynamicLighting; From 539eca35bc356c92fd0c55d9094452f7defb44ca Mon Sep 17 00:00:00 2001 From: ggetz Date: Mon, 25 Nov 2024 13:25:54 -0500 Subject: [PATCH 4/4] Specs --- .../engine/Specs/Scene/DynamicEnvironmentMapManagerSpec.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/engine/Specs/Scene/DynamicEnvironmentMapManagerSpec.js b/packages/engine/Specs/Scene/DynamicEnvironmentMapManagerSpec.js index de82af7a9478..0783774b7482 100644 --- a/packages/engine/Specs/Scene/DynamicEnvironmentMapManagerSpec.js +++ b/packages/engine/Specs/Scene/DynamicEnvironmentMapManagerSpec.js @@ -2,6 +2,7 @@ import { Cartesian3, Cartographic, Color, + ContextLimits, CubeMap, DynamicAtmosphereLightingType, DynamicEnvironmentMapManager, @@ -70,16 +71,20 @@ describe("Scene/DynamicEnvironmentMapManager", function () { () => { const time = JulianDate.fromIso8601("2024-08-30T10:45:00Z"); - let scene; + let scene, orginalMaximumCubeMapSize; beforeAll(() => { scene = createScene({ skyBox: false, }); + orginalMaximumCubeMapSize = ContextLimits.maximumCubeMapSize; + // To keep tests fast, don't throttle + ContextLimits._maximumCubeMapSize = Number.POSITIVE_INFINITY; }); afterAll(() => { scene.destroyForSpecs(); + ContextLimits._maximumCubeMapSize = orginalMaximumCubeMapSize; }); afterEach(() => {