Repository navigation
Conversation
engine.asynchronousPipelineCreation (off by default): a mesh draw whose render pipeline is not created yet is omitted while the pipeline is created asynchronously, through the existing preWarmPipeline(), instead of the frame waiting for a synchronous creation. With the option off, nothing changes. - Only draws of meshes can wait. The draw contexts of a mesh, in every pass that draws it (shadow maps and ShadowDepthWrapper included), share a small state of the mesh, linked once when the pass's draw wrapper is created. A mesh is shown in all its passes together. Post-processes and the other draws keep creating their pipelines synchronously. - Snapshots never replay a frame that misses a draw forever: a recording that omitted draws is recorded again at the first frame after their pipelines are created, and an incoherent one at the next frame (invalidateRecording()). - preWarmPipeline() creates a configuration once (pending creations kept by cache token, shared with createRenderPipelineAsync()), reports a failure and creates that configuration synchronously at its next miss, and its late completions are ignored once the device is replaced or the engine disposed. - areAllEffectsReady() is false while pipelines are being created. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A mesh draw may wait for its pipeline only in a pass the scene renders again: its cameras, the render targets of its frame and the frame graph (AbstractEngine._drawsMayWait). A render target with REFRESHRATE_RENDER_ONCE, a screenshot through a render target and any render outside the scene's frame draw every mesh in that rendering (AbstractEngine._renderingOnce), creating their pipelines synchronously: such a pass is not rendered again, so a waiting draw would be missing from it for good. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…reset are ignored A reset of the pipeline cache (a device change, the last WebGPU engine disposed) now forgets the pending creations too, not only the failed ones: one that completes later, on the device it was started on, is neither kept, counted, nor reported, and NumPendingPipelines no longer waits for creations of a device that is gone. Both tests fail with the reset as it was. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…settings The option and its state are declared after disableCacheRenderPipelines, with the settings of the pipeline cache, instead of between snapshotRenderingReset() and snapshotRendering. Only the place changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
deltakosh
left a comment
There was a problem hiding this comment.
Hello @enriqxxx,
One gap in the shadow pass: ShadowDepthWrapper._makeEffect() stores mainDrawWrapper directly in params.drawWrapper[passIdForDrawWrapper] (shadowDepthWrapper.ts:221-231), so the new _pipelineWait link at line 161 is only set for the wrappers of additional passes. With a single shadow generator, which is the common case, the shadow-map draw never waits with the mesh: its pipeline is created synchronously, and the shadow of a mesh whose main draw is still waiting is rendered. Could you link mainDrawWrapper too (when subMesh is known), with a test?
Thanks!
…Wrapper waits with its mesh From the review of BabylonJS#19090: ShadowDepthWrapper._makeEffect() creates the draw wrapper of the pass it is first called for and stores it directly, so getEffect() never linked it to the mesh's wait state, and with a single shadow generator the shadow-map draw did not wait with its mesh. It is linked when it is created now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks, you are right. The description says that the draw contexts of a mesh wait "in every pass that draws it (main pass, shadow maps, also through a Fixed in c358594: The branch's own tests: 42 passed. All the core unit tests on 8958948 (296 files): 4,860 passed, 1 expected fail (already marked on master). Prepared by Mema, the AI of our studio 53ON (Claude in the 53ON harness, with MemoriaBios, our long-term memory), and reviewed by me. |
deltakosh
left a comment
There was a problem hiding this comment.
Hello @enriqxxx,
Thanks for the update. ShadowDepthWrapper now gives the first shadow pass's mainDrawWrapper the same _pipelineWait (the mesh's _internalAbstractMeshDataInfo) as the other passes, so a single shadow pass waits on asynchronous pipeline creation too. The new test covers the first and the additional passes. This resolves my previous request.
LGTM, thanks!
|
/azp run |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
…ission, and every new scope is exception-safe Mesh._processRendering gives the draws it submits their owner (AbstractEngine._pipelineWaitOwner), in all the passes of the mesh and whatever draw context its material gives them, so a ShaderMaterial(..., false), whose draw context its meshes share, waits with their shadow and depth passes. Any other draw has no owner and never waits: the bounding box of an occlusion query draws synchronously, so its query never closes empty (in a FAST snapshot recording that culled a visible mesh, with no creation left to record it again). The draw contexts no longer carry a wait state: IDrawContext, WebGPUDrawContext, SubMesh and ShadowDepthWrapper are back to master. The render target rendered once, the frame graph, the render targets of the frame and the cameras restore their scope in finally blocks. Tests through the engine's own draw path (drawElementsType, beginFrame, endFrame, the end of each render pass, its snapshot rendering and bundle list; only the GPU and the completion of the asynchronous creations are faked), with draw contexts from real sub meshes, materials and draw wrappers: deferred completion, the holdback of a cached bundle, the draws that never wait, an occlusion query's bounding box in a FAST recording and its replays, the snapshot's recovery. On the previous commit the material-context and occlusion tests fail and the 6 others pass; removing any piece of the dispatch wiring makes one fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Playground #XX5SUM#1 (WebGPU only) and four entries sharing one reference image, generated with synchronous creation: asynchronous creation in and out of the compatibility mode, with snapshot rendering (recorded while the pipelines are created), and synchronous creation. The meshes take each path a mesh draw can take, all casting shadows: thin instances, a ShaderMaterial(..., false) shared by two meshes, a ShadowDepthWrapper and a render target rendered once. The Playground renders until the scene has been ready for three frames (a mesh comes back the frame after its last wait), so the frames of the test show every mesh in all its passes, and it gives the engine its creation mode back when the scene is disposed. The four entries also pass with zero tolerance. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the review. Fixed in 22d6e9b, with the visualization test in eebfb1e. The root change: the owner of a draw is now supplied at submission, not stored on a draw context. Keep occlusion-query bounding-box draws synchronous and outside mesh holdback. Handle mesh draws whose wrapper belongs to the material. With the owner supplied at submission, the draw of a Restore the new engine-wide render-scope state in Exercise the actual opt-in draw path and complete frame transitions. The suite now runs the engine's own
On c358594 the tests of points 1 and 2 fail and the 6 others pass. Removing a piece of the dispatch wiring makes the suite fail: a Visualization: Playground #XX5SUM#1 and four
The meshes take each path a mesh draw can take, all casting shadows: thin instances, two meshes sharing a All the core unit tests on 22d6e9b (296 files): 4,865 passed, 1 expected fail (already marked on master). tsc and prettier are clean; eslint reports no errors. Prepared by Mema, the AI of our studio 53ON (Claude in the 53ON harness, with MemoriaBios, our long-term memory), and reviewed by me. |
deltakosh
left a comment
There was a problem hiding this comment.
Hello @enriqxxx,
Moving the wait ownership to draw submission and restoring the rendering scopes in finally blocks closes the remaining gaps, with dispatch, exception-recovery and visualization coverage. Still approved.
Thanks!
|
/azp run |
|
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command. |
…which can show a mesh in some of its passes only The passes rendered before the first wait of a mesh in a frame have already drawn it, so that frame can show, for instance, the shadow of a mesh whose main pass draw waits. The option's doc said it in a parenthesis; it now says it as a limitation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es renders in every frame while mesh draws wait A target with refreshRate > 1 could keep an incomplete render. It could omit a mesh that waited and not render again until its next refresh, while the main pass drew the mesh two frames after its last wait; or it could draw the mesh in the frame of the first wait and keep it while the main pass held the mesh back. ObjectRenderer.shouldRender() now also renders such a target in every frame until the meshes that waited are drawn again (two frames after the last wait), so it holds a mesh back and shows it in the same frames as the other passes, then goes back to its rate. For that, the last frame in which a mesh draw waited moves to AbstractEngine. With a snapshot every target already renders in every frame, and with the option off that frame never moves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Two more changes, from the forum discussion of this PR (64471 #3). The frame of the first wait, documented. deca66d makes it a limitation in the option's doc, where it was only a parenthesis. In the frame where a draw of a mesh first waits, the passes rendered before it have already drawn the mesh, so that frame can show the mesh in some of its passes only (the shadow of a mesh whose main pass draw waits, for instance). Render targets refreshed every N frames. Fixed in a2780c0. A target with
On deca66d both fail in the frame after the first wait, where the shadow map is not rendered: it keeps the render that omitted the mesh, or the shadow of the mesh the main pass holds back. All the core unit tests on a2780c0 (296 files): 4,867 passed, 1 expected fail (already marked on master). tsc reports nothing new and nothing in Prepared by Mema, the AI of our studio 53ON (Claude in the 53ON harness, with MemoriaBios, our long-term memory), and reviewed by me. |
deltakosh
left a comment
There was a problem hiding this comment.
Hello @enriqxxx,
While mesh draws wait for their pipelines, render targets refreshed every N frames now render in every frame (refreshRate > 1 only), so the mesh appears in the same frames across passes, and the first-wait limitation is documented. Covered for both pass orderings. Still approved.
Thanks!
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
Please make sure to label your PR with "bug", "new feature" or "breaking change" label(s). |
|
Snapshot stored with reference name: Test environment: To test a playground add it to the URL, for example: https://snapshots-cvgtc2eugrd3cgfd.z01.azurefd.net/refs/pull/19090/merge/index.html#WGZLGJ#4600 Links to test your changes to core in the published versions of the Babylon tools (does not contain changes you made to the tools themselves): https://playground.babylonjs.com/?snapshot=refs/pull/19090/merge To test the snapshot in the playground with a playground ID add it after the snapshot query string: https://playground.babylonjs.com/?snapshot=refs/pull/19090/merge#BCU1XR#0 If you made changes to the sandbox or playground in this PR, additional comments will be generated soon containing links to the dev versions of those tools. |
🔴 Memory Leak Test Results0 passed, 1 leaked out of 1 scenarios Leaked Scenarios
|
|
Visualization tests for WebGPU |
|
Visualization tests for WebGPU |
|
WebGL2 visualization test reporter: |
⚡ Performance Test Results🟢 All performance tests passed — no regressions detected. |
…per request, so it draws every mesh The picking target kept the refresh rate of 1 in the scene's custom render targets, so its mesh draws could wait for their pipelines, or be held back with another pass of their mesh. The picker counts the meshes it binds, not the draws, so on a first pick it read a target without them, and no creation could repair the finished request: pickAsync returned null and multi-picking missed. Each request now sets the target to REFRESHRATE_RENDER_ONCE, whose rendering draws every mesh and creates the pipelines it lacks synchronously, and asks for another rendering only while the picking shaders are not ready. The tests pick through the scene's render targets and its draws, with the GPU replaced: a first pick with the picking pipeline new, and two multi-picks while the mesh waits in the camera's pass, the second one with that pipeline created. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… wait only in a rendering its shouldRender() asked for A direct call to render() inherited the scope of the pass it was called from: MeshUVSpaceRenderer.renderTexture(), called from an observer of the camera's pass, rendered its target with mesh draws that could wait, and nothing rendered it again. shouldRender() now marks the rendering it asks for (the scene's render targets, the frame graph's tasks) until finishRender(), and render() lets the mesh draws wait only in that rendering: any other one renders once, as with REFRESHRATE_RENDER_ONCE. The voxelization of IBL shadows renders its targets that way, so it no longer needs its own scope. The test stamps a decal from an observer of the camera's pass, with the pipeline of its target new. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s has no owner Mesh._draw notified onBeforeDrawObservable (and so the onBeforeDraw callback) inside the scope of the mesh's owner, so an EffectRenderer pass drawn from there waited with the mesh and could be omitted, losing an initialization done once. The owner is cleared while the observers are notified, and given back for the mesh's draw. The test draws an EffectRenderer pass from such an observer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…esh wait with the mesh The color and depth compositors of point splatting each had their own owner, so enabling point depth once the color had warmed showed the splats without depth for the whole compilation of the depth compositor. A mesh can now wait with the draws of another (_InternalAbstractMeshDataInfo._pipelineWaitOwner), and both compositors wait with their splat mesh. The test draws them as the controller does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rgets costs while pipelines are created The docs of asynchronousPipelineCreation and ObjectRenderer.shouldRender said that a render target refreshed every N frames renders in every frame while mesh draws wait, but not its reach: every such render target of the engine renders so, including those that do not draw the meshes that wait, and up to the frame in which the meshes are drawn again, two frames after the last wait (the recovery frame included). Both now say it, and that these extra renders are the cost of the option while pipelines are created. The option's list of the passes rendered once also gets those of the previous commits: a GPU picking (REFRESHRATE_RENDER_ONCE), and an object renderer rendered by a direct call to render() that its shouldRender() did not ask for (the voxelization of IBL shadows, a decal of MeshUVSpaceRenderer). Comments only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks @deltakosh and @Popov72, everything is addressed in four commits, one per point: 169e023, 15f1d67, 8914308 and 6b5be44. A fifth one, d34a278 (comments only), documents what the refresh of render targets costs while pipelines are created, as @Popov72 asked in the forum: every render target of the engine refreshed every N frames renders in every frame, including those that do not draw the meshes that wait, up to the frame in which they are drawn again (two frames after the last wait, that recovery frame included). 1. GPU picking never waits (169e023). The picking target was in the scene's custom render targets with a refresh rate of 1, so its mesh draws could wait, or be held back with another pass of their mesh, while The tests pick through the scene's custom render targets and its draws, with the GPU replaced. The pixels of the picking target hold the
On 54d8a8c, 2. A direct Test: 3. The observers of a draw (8914308). Test: Minor (6b5be44). Both compositors now share the owner of their splat mesh. A mesh can wait with the draws of another mesh ( Test: All the core unit tests on 6b5be44 (296 files): 4,878 passed, 1 expected fail (already marked on master); d34a278 changes comments only. tsc of core reports 0 errors in a full check, and nothing in the test files this touches; prettier is clean, and eslint reports no new warning. Prepared by Mema, the AI of our studio 53ON (Claude in the 53ON harness, with MemoriaBios, our long-term memory), and reviewed by me. |
deltakosh
left a comment
There was a problem hiding this comment.
Hello @enriqxxx,
The four points of my last review are resolved: GPU picking draws every mesh, only a rendering that shouldRender() asked for lets draws wait, observer draws have no owner, and the point-splatting compositors wait with their mesh. Approving.
Two small things before it can land:
packages/dev/core/test/unit/Meshes/babylon.mesh.pipelineWaitOwner.test.ts:44usesengine.drawElementsType.bind(engine);Function.bindis prohibited underpackages/dev/**(.github/instructions/prohibited-apis.instructions.md). Please capture the original method and call it from an arrow with.call(engine, ...).- The branch conflicts with master, so CI did not run at this head; please rebase so the checks can confirm it.
Thanks!
…tion-r2 Conflict in webgpuEngine.pure.ts: master's _getTextureState and this branch's _omitDraw and _forgetPendingPipelines were added at the same place. All are kept, and _draw takes the wait path with the shared helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The original method is captured and called with .call(engine, ...), as .github/instructions/prohibited-apis.instructions.md asks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Through the real WebGPUEngine draw with a command recorder provider: no record while the pipeline is created, nor in the frame it is held back for the other passes of its mesh, then one draw, once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Head branch was pushed to by a user without write access
|
Thanks @deltakosh, both are done.
@matthargett, the shared texture-state helper stays on the wait path: On bf7fec3, the core unit suite passes (317 files, 5,169 tests, plus the expected fail already marked on master), and core type-checks with no errors. Prepared by Ramon and Mema, the AI of our studio 53ON (🖐️, 👀, 🧮🔨🪛, 🧠 human memory simulation). |
deltakosh
left a comment
There was a problem hiding this comment.
Re-reviewed d34a278...bf7fec3.
- Master merge 9d036e0: I re-ran the 3-way merge on every file both sides touched (
abstractEngine.pure.ts,mesh.pure.ts, visualizationconfig.json,webgpuEngine.pure.ts). The only conflict is inwebgpuEngine.pure.ts, where_omitDraw/snapshot hold-back and master's_getTextureState(#19107) were added at the same spot. The resolution keeps both, unchanged. - c90a42f: the test no longer uses
Function.bind. It captures the method and calls it with.call(engine, ...), per the prohibited-APIs instructions. - bf7fec3: a new test runs the real
WebGPUEnginedraw with a command recorder provider. It checks that a draw is neither recorded nor counted while its pipeline is pending or held back for one frame, and is recorded exactly once after that.
The CodeQL alerts on this head are in existing regexes in shaderProcessor.ts, webgpuShaderProcessorsWGSL.pure.ts and fileTools.pure.ts. None of those files is changed by this PR.
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
Reviewer - this PR has made changes to one or more package.json files. |
🟢 Memory Leak Test Results4 passed, 0 leaked out of 4 scenarios 🟢 All memory leak tests passed — no leaks detected. Passed Scenarios (4)
|
|
WebGL2 visualization test reporter: |
⚡ Performance Test Results🟢 All performance tests passed — no regressions detected. |
|
Visualization tests for WebGPU |
Popov72
left a comment
There was a problem hiding this comment.
Reviewed the complete focused delta and affected rendering integration against the validated baseline. No actionable findings.
Forum: 64298 #11, "Async pipelines: extend the existing path, and handle incomplete frames".
On WebGPU, the first draw that needs a new render pipeline creates it synchronously: the browser compiles the shaders for that pipeline state before it runs the commands that follow, and the frame waits for it (here about 100 ms per new PBR variant). This PR adds an opt-in,
engine.asynchronousPipelineCreation(off by default): a mesh draw whose pipeline is not created yet is omitted while the pipeline is created asynchronously, through the existingpreWarmPipeline(). With the option off, nothing changes.What the option does.
Mesh._processRenderinggives the draws it submits their owner, the mesh, in every pass that draws it (main pass, shadow maps, also through aShadowDepthWrapper, depth renderer, geometry buffer, effect layers, outline, volumetric light scattering) and whatever draw context its material gives them, but not to what an observer of its draw (onBeforeDrawObservable) draws;_drawreads the owner from the engine. The color and depth compositors of point splatting wait with their splat mesh. Post-processes, effect renderers, sprites, particles, the bounding boxes of occlusion queries and the other draws have none, and keep creating their pipelines synchronously, so a full-screen pass is never skipped and a query never closes empty. Some of them run only once: the environment BRDF table, for one, is expanded from RGBD by a single post-process render (RGBDTextureTools.ExpandRGBDTexture). In our app, an earlier version of this option that let any draw wait omitted that render: the table stayed at zero, and every PBR material drew black while its depth was still written.customRenderTargets, the render targets of its materials and components) and the frame graph. An object renderer's mesh draws wait only in a rendering itsshouldRender()asked for. A pass rendered once draws every mesh in that rendering, creating its pipelines synchronously, so it never misses a mesh. That covers an object renderer withREFRESHRATE_RENDER_ONCE(of a render target, or of a frame graph task), such as a GPU picking (its target renders once per request); an object renderer rendered by a direct call torender(), such as the voxelization of IBL shadows or a decal ofMeshUVSpaceRenderer; a screenshot through a render target (CreateScreenshotUsingRenderTargetrenders the scene's frame once into its target), and any render outside the scene's frame.asynchronousPipelineCreationandObjectRenderer.shouldRender()). Only the passes drawn before the first wait of that frame show it alone, for that frame (in the test below, the shadow map draws of a new mesh, one frame).areAllEffectsReady()(soscene.isReady()) is false while the pipelines this engine waits for are being created: its own creations, and a creation by another engine of a pipeline it draws too.On top of the existing path. No second cache:
WebGPUCacheRenderPipeline.tryGetRenderPipeline()looks up the same cache and, on a miss, callspreWarmPipeline(), which nowcreateRenderPipelineAsync()) gets the pending promise;numAwaitedPipelines), which the engine's readiness reads; the cache forgets them when its device is lost or its engine is disposed.NumPendingPipelinescounts those of all the engines, for diagnostics;Logger.Errorwith the pipeline label; callers still get the rejection), and the next miss of that configuration creates it synchronously, so the WebGPU error shows exactly as today;NumPendingPipelinesdoes not wait for creations of a device that is gone; the engine also ignores the completions of an older generation (its own dispose included).It is named asynchronous pipeline creation: the browser may still do part of the compilation elsewhere or at first use; nothing here claims the whole cost left the main thread.
Measured against 9.33.0 on 87250b7, which 9.33.0 contains (the branch is on master 8958948; the commits since, the KTX2 encoder of #19011 and the version file, touch nothing measured here), the machine checked quiet before and during each measure. Chromium 147, RTX 3080 Laptop GPU, WebGPU, 800×450. Deterministic scene: 600 meshes with a StandardMaterial, a directional light with a 4-cascade CSM; every 40 frames a wave of 20 meshes appears with a PBR variant no draw has used yet (albedo and emissive textures, clear coat, sheen, iridescence: a new shader, so a new main-pass pipeline). 8 waves, 360 frames per run, a new browser for every run, 2 rounds in alternating order, in three modes: no snapshot,
compatibilityMode = false, and a FAST snapshot recorded again at each wave. A frame's time is from its start until the GPU finished it (onSubmittedWorkDone).compatibilityMode = false/ FAST snapshot)asynchronousPipelineCreationcompatibilityMode = false, with differences of both signs between rounds; and with the option off, this branch matches 9.33.0 (9.86 / 9.95 ms, 7.71 / 7.55 ms). With it off, a draw reads one boolean more, and the pipeline cache's synchronous path is unchanged. A first version that kept the wait state on the mesh and read it at every draw cost 0.4-0.7 ms per frame in this scene (the time was in_draw, in a CPU profile); the state is now read only in a frame in which a draw waited and the one after.Prewarming known content instead. The same waves, prewarmed as an app would: the effect of the real sub mesh (
isReadyForSubMesh;forceCompilationAsync()compiles with a temporary sub mesh), thencreateRenderPipelineAsync({ effect, mesh })with the default states; the frames go on and the wave is shown when the promise resolves. It removes the long frames too, but it does not create the pipeline the wave draws with: the first draw of each wave still creates one synchronously. The new meshes receive the CSM, whose shadow map is a depth texture (shadowTexture0, DEPTH32_FLOAT), so their draws use texture state 1, andcreateRenderPipelineAsync()always prewarms texture state 0. That synchronous creation was short here only because the browser had compiled the same shaders during the prewarm. The automatic handling creates exactly the pipelines the draws ask for, in every pass (shadow maps included, whose effects the app does not see), without knowing the content in advance; the price is the omitted draws. LettingcreateRenderPipelineAsync()take the texture state (or derive it from a sub mesh) would be a separate change.Files.
webgpuCacheRenderPipeline.ts: pending creations by cache token, the failure report and the synchronous creation after it,tryGetRenderPipeline()andlastPendingPipeline, the creations each engine's cache waits for (numAwaitedPipelines),NumPendingPipelines.webgpuCacheRenderPipelineTree.ts:ResetCache()forgets the failures.webgpuEngine.pure.ts:asynchronousPipelineCreation, the omitted draws, the snapshot recorded again,areAllEffectsReady(), device loss and dispose.webgpuSnapshotRendering.ts:invalidateRecording()(the frame is recorded again at the next frame, the existing mechanism of a restarted render pass).mesh.pure.ts: the owner of the draws_processRenderingsubmits, given back in afinally; none while the observers of a draw are notified; the owner of another mesh for a mesh that waits with it.abstractMesh.pure.ts: the two frames the mesh remembers (in its internal data, so nothing is allocated), and the mesh it waits with.gaussianPointSplattingController.pure.ts: the compositors wait with their splat mesh.gpuPicker.ts: each request renders the picking target once.abstractEngine.pure.ts,scene.pure.ts,objectRenderer.ts,screenshotTools.pure.ts: the passes whose mesh draws may wait (_drawsMayWait), the rendering an object renderer'sshouldRender()asked for, the renderings done once (_renderingOnce), and the last frame a mesh draw waited (_lastPipelineWaitFrameId), with whichObjectRenderer.shouldRender()renders a target refreshed every N frames in every frame.Unit tests.
webgpuCacheRenderPipeline.test.ts, 10 more: a configuration created once while it is pending;tryGetRenderPipeline()returns null while the pipeline is created, then the cached one; it shares a creation started bypreWarmPipeline(); a failed creation is reported and the next miss creates the pipeline synchronously; a disabled cache creates synchronously; a creation pending when the cache is reset is forgotten, and ignored when it completes; one that fails after the reset is neither reported nor marked as failed. Removing the deduplication fails 3 of them, removing the synchronous creation after a failure fails 1, and the reset as it was (failed creations only) fails the last 2. With two engines: one stays ready while the other creates a pipeline it does not draw; one waits for a pipeline the other is creating once it draws it too; an engine forgets its creations when its device is lost.webgpuEngine.asynchronousPipelineCreation.test.ts, 15, through the engine's own draw path with the GPU replaced: a mesh omitted in all its passes and drawn in all of them in the same frame (also with a cached bundle, and two meshes that share their material's draw context); the draws that never wait (no owner, a pass rendered once); a shadow map refreshed every 4 frames while its mesh waits there or in the main pass; a frame graph object renderer rendered once and the voxelization of IBL shadows, which keep the mesh after the creations complete (the scene's frame and its renderers run as they are); a GPU picking and two multi-pickings with the picking pipeline new, through the scene's render targets, which read the mesh (the pixels hold the mesh id of the draws that reached the GPU); a decal stamped by a direct call from the camera's pass; the bounding box of an occlusion query in a FAST snapshot; recordings made again; a creation that completes after a device loss.webgpuSnapshotRendering.test.ts, 1 more:invalidateRecording().webgpuEngine.dispose.test.ts, 1 more: the engine's cache forgets the creations it waits for.babylon.scene.drawsMayWait.test.ts, 6: the draws of the scene's cameras may wait during the frame only; those of a render target of the frame may, those of one rendered once never, also from inside a camera's pass; a render outside the frame never; a screenshot through a render target never, nor the render targets of the frame it renders; every scope is restored when a rendering throws, the frame graph's too.babylon.mesh.pipelineWaitOwner.test.ts, 5: each mesh gets its own owner in its shadow map and main passes, with its own draw contexts or its material's; the bounding box of an occlusion query gets none, nor does what an observer of a draw draws; the compositors of point splatting get their splat mesh's; the owner is given back after the draws of a mesh, also when one of them throws.Visualization test. "WebGPU asynchronous pipeline creation - every mesh in all its passes" (#XX5SUM#1), against one reference image: in compatibility mode, in non-compatibility mode, with snapshot rendering, and with the option off.
Prepared by Mema, the AI of our studio 53ON (Claude in the 53ON harness, with MemoriaBios, our long-term memory), and reviewed by me.
🤖 Generated with Claude Code