Skip to content

Commit 3adf819

Browse files
mvaligurskyMartin Valigursky
andauthored
Refactor rasterize variant management and beforePasses scheduling (#8579)
Move rasterize shader/BindGroupFormat/Compute creation from GSplatComputeLocalRenderer into GSplatLocalDispatchSet behind a Map-based variant cache (getRasterizeCompute(key)), allowing lazy per-variant creation without duplicating heavy buffers. Move beforePasses scheduling from ForwardRenderer.buildFrameGraph() into RenderPassForward.updateCameraBeforePasses(), using the existing firstCameraUse flag to guarantee exactly-once collection per camera. Add updateCameraUseFlags() to FramePassCameraFrame so that CameraFrame's internally created RenderPassForward instances have correct firstCameraUse/lastCameraUse flags, fixing EVENT_PRERENDER/EVENT_POSTRENDER not firing in the CameraFrame path. Made-with: Cursor Co-authored-by: Martin Valigursky <mvaligursky@snapchat.com>
1 parent 0afe3a6 commit 3adf819

6 files changed

Lines changed: 184 additions & 137 deletions

File tree

src/extras/render-passes/frame-pass-camera-frame.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,45 @@ class FramePassCameraFrame extends FramePass {
294294

295295
const allPasses = this.collectPasses();
296296
this.beforePasses = allPasses.filter(element => element !== undefined && element !== null);
297+
298+
this.updateCameraUseFlags();
299+
}
300+
301+
/**
302+
* Scan all RenderPassForward instances in the pass chain and mark the first / last
303+
* render action per camera with firstCameraUse / lastCameraUse. This mirrors what
304+
* LayerComposition does for the non-CameraFrame path and ensures that beforePasses
305+
* collection and EVENT_PRERENDER / EVENT_POSTRENDER fire exactly once per camera.
306+
*
307+
* @private
308+
*/
309+
updateCameraUseFlags() {
310+
const firstSeen = new Map();
311+
const lastSeen = new Map();
312+
313+
for (let i = 0; i < this.beforePasses.length; i++) {
314+
const pass = this.beforePasses[i];
315+
if (pass instanceof RenderPassForward) {
316+
const actions = pass.renderActions;
317+
for (let j = 0; j < actions.length; j++) {
318+
const ra = actions[j];
319+
const cam = ra.camera;
320+
if (cam) {
321+
if (!firstSeen.has(cam)) {
322+
firstSeen.set(cam, ra);
323+
}
324+
lastSeen.set(cam, ra);
325+
}
326+
}
327+
}
328+
}
329+
330+
firstSeen.forEach((ra) => {
331+
ra.firstCameraUse = true;
332+
});
333+
lastSeen.forEach((ra) => {
334+
ra.lastCameraUse = true;
335+
});
297336
}
298337

299338
collectPasses() {

src/scene/camera.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,11 @@ class Camera {
6969
framePasses = [];
7070

7171
/**
72-
* Frame passes that execute before this camera's main rendering. These are added to the
73-
* frame graph at first camera use, before any render actions or framePasses.
72+
* Frame passes that execute before this camera's main scene rendering. Each entry contains
73+
* a pass and metadata controlling scheduling. Entries are picked up by the RenderPassForward
74+
* that renders this camera's layers.
7475
*
75-
* @type {FramePass[]}
76+
* @type {{ pass: FramePass, requiresDepth: boolean }[]}
7677
*/
7778
beforePasses = [];
7879

src/scene/gsplat-unified/gsplat-compute-local-renderer.js

Lines changed: 29 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { Vec2 } from '../../core/math/vec2.js';
22
import { Compute } from '../../platform/graphics/compute.js';
33
import { Shader } from '../../platform/graphics/shader.js';
44
import { StorageBuffer } from '../../platform/graphics/storage-buffer.js';
5-
import { BindGroupFormat, BindStorageBufferFormat, BindStorageTextureFormat, BindUniformBufferFormat } from '../../platform/graphics/bind-group-format.js';
5+
import { BindGroupFormat, BindStorageBufferFormat, BindUniformBufferFormat } from '../../platform/graphics/bind-group-format.js';
66
import { UniformBufferFormat, UniformFormat } from '../../platform/graphics/uniform-buffer-format.js';
77
import {
88
BUFFERUSAGE_COPY_DST,
9-
PIXELFORMAT_R32U, PIXELFORMAT_RGBA16F, PIXELFORMAT_RGBA16U,
9+
PIXELFORMAT_RGBA16U,
1010
SHADERLANGUAGE_WGSL,
1111
SHADERSTAGE_COMPUTE,
1212
UNIFORMTYPE_FLOAT,
@@ -16,11 +16,9 @@ import {
1616
import { GSPLAT_FORWARD, PROJECTION_ORTHOGRAPHIC } from '../constants.js';
1717
import { Mat4 } from '../../core/math/mat4.js';
1818
import { GSplatRenderer } from './gsplat-renderer.js';
19-
import { shaderChunksWGSL } from '../shader-lib/wgsl/collections/shader-chunks-wgsl.js';
2019
import { FramePassGSplatComputeLocal } from './frame-pass-gsplat-compute-local.js';
2120
import { computeGsplatLocalTileCountSource } from '../shader-lib/wgsl/chunks/gsplat/compute-gsplat-local-tile-count.js';
2221
import { computeGsplatLocalScatterSource } from '../shader-lib/wgsl/chunks/gsplat/compute-gsplat-local-scatter.js';
23-
import { computeGsplatLocalRasterizeSource } from '../shader-lib/wgsl/chunks/gsplat/compute-gsplat-local-rasterize.js';
2422
import { computeGsplatLocalTileSortSource } from '../shader-lib/wgsl/chunks/gsplat/compute-gsplat-local-tile-sort.js';
2523
import { computeGsplatLocalClassifySource } from '../shader-lib/wgsl/chunks/gsplat/compute-gsplat-local-classify.js';
2624
import { computeGsplatLocalBucketSortSource } from '../shader-lib/wgsl/chunks/gsplat/compute-gsplat-local-bucket-sort.js';
@@ -166,15 +164,6 @@ class GSplatComputeLocalRenderer extends GSplatRenderer {
166164
/** @type {BindGroupFormat} */
167165
_chunkSortBindGroupFormat;
168166

169-
/** @type {Shader} */
170-
_rasterizeColorShader;
171-
172-
/** @type {Shader|null} */
173-
_rasterizePickShader = null;
174-
175-
/** @type {BindGroupFormat} */
176-
_rasterizeColorBindGroupFormat;
177-
178167
/**
179168
* @param {GraphicsDevice} device - The graphics device.
180169
* @param {GraphNode} node - The graph node.
@@ -221,9 +210,6 @@ class GSplatComputeLocalRenderer extends GSplatRenderer {
221210
this._copyBindGroupFormat.destroy();
222211
this._chunkSortShader.destroy();
223212
this._chunkSortBindGroupFormat.destroy();
224-
this._rasterizeColorShader.destroy();
225-
this._rasterizePickShader?.destroy();
226-
this._rasterizeColorBindGroupFormat.destroy();
227213

228214
this._projCacheBuffer?.destroy();
229215
this._tileEntriesBuffer?.destroy();
@@ -287,10 +273,11 @@ class GSplatComputeLocalRenderer extends GSplatRenderer {
287273

288274
/** @private */
289275
_registerFramePass() {
290-
const beforePasses = this.cameraNode.camera?.camera?.beforePasses;
291-
if (beforePasses) {
292-
if (beforePasses.indexOf(this.framePass) === -1) {
293-
beforePasses.push(this.framePass);
276+
const camera = this.cameraNode.camera?.camera;
277+
if (camera) {
278+
const exists = camera.beforePasses.some(e => e.pass === this.framePass);
279+
if (!exists) {
280+
camera.beforePasses.push({ pass: this.framePass, requiresDepth: false });
294281
}
295282
this._needsFramePassRegister = false;
296283
} else {
@@ -301,11 +288,11 @@ class GSplatComputeLocalRenderer extends GSplatRenderer {
301288
/** @private */
302289
_unregisterFramePass() {
303290
this._needsFramePassRegister = false;
304-
const beforePasses = this.cameraNode.camera?.camera?.beforePasses;
305-
if (beforePasses) {
306-
const idx = beforePasses.indexOf(this.framePass);
291+
const camera = this.cameraNode.camera?.camera;
292+
if (camera) {
293+
const idx = camera.beforePasses.findIndex(e => e.pass === this.framePass);
307294
if (idx !== -1) {
308-
beforePasses.splice(idx, 1);
295+
camera.beforePasses.splice(idx, 1);
309296
}
310297
}
311298
}
@@ -590,26 +577,28 @@ class GSplatComputeLocalRenderer extends GSplatRenderer {
590577
device.computeDispatch([set.chunkSortCompute], pickMode ? 'GSplatPickChunkSort' : 'GSplatLocalChunkSort');
591578

592579
// --- Pass 5: Rasterize ---
593-
set.rasterizeCompute.setParameter('screenWidth', width);
594-
set.rasterizeCompute.setParameter('screenHeight', height);
595-
set.rasterizeCompute.setParameter('numTilesX', numTilesX);
596-
set.rasterizeCompute.setParameter('nearClip', cam.nearClip);
597-
set.rasterizeCompute.setParameter('farClip', cam.farClip);
598-
set.rasterizeCompute.setParameter('alphaClip', alphaClip);
599-
set.rasterizeCompute.setParameter('tileEntries', this._tileEntriesBuffer);
600-
set.rasterizeCompute.setParameter('tileSplatCounts', set._tileSplatCountsBuffer);
601-
set.rasterizeCompute.setParameter('projCache', this._projCacheBuffer);
602-
set.rasterizeCompute.setParameter('rasterizeTileList', set._rasterizeTileListBuffer);
603-
set.rasterizeCompute.setParameter('tileListCounts', set._tileListCountsBuffer);
580+
const variantKey = pickMode ? 'pick' : 'color';
581+
const rasterizeCompute = set.getRasterizeCompute(variantKey);
582+
rasterizeCompute.setParameter('screenWidth', width);
583+
rasterizeCompute.setParameter('screenHeight', height);
584+
rasterizeCompute.setParameter('numTilesX', numTilesX);
585+
rasterizeCompute.setParameter('nearClip', cam.nearClip);
586+
rasterizeCompute.setParameter('farClip', cam.farClip);
587+
rasterizeCompute.setParameter('alphaClip', alphaClip);
588+
rasterizeCompute.setParameter('tileEntries', this._tileEntriesBuffer);
589+
rasterizeCompute.setParameter('tileSplatCounts', set._tileSplatCountsBuffer);
590+
rasterizeCompute.setParameter('projCache', this._projCacheBuffer);
591+
rasterizeCompute.setParameter('rasterizeTileList', set._rasterizeTileListBuffer);
592+
rasterizeCompute.setParameter('tileListCounts', set._tileListCountsBuffer);
604593
if (pickMode) {
605-
set.rasterizeCompute.setParameter('pickIdTexture', set.pickIdTexture);
606-
set.rasterizeCompute.setParameter('pickDepthTexture', set.pickDepthTexture);
594+
rasterizeCompute.setParameter('pickIdTexture', set.pickIdTexture);
595+
rasterizeCompute.setParameter('pickDepthTexture', set.pickDepthTexture);
607596
} else {
608-
set.rasterizeCompute.setParameter('outputTexture', set.outputTexture);
597+
rasterizeCompute.setParameter('outputTexture', set.outputTexture);
609598
}
610599

611-
set.rasterizeCompute.setupIndirectDispatch(indirectSlot + 2);
612-
device.computeDispatch([set.rasterizeCompute], pickMode ? 'GSplatPickRasterize' : 'GSplatLocalRasterize');
600+
rasterizeCompute.setupIndirectDispatch(indirectSlot + 2);
601+
device.computeDispatch([rasterizeCompute], pickMode ? 'GSplatPickRasterize' : 'GSplatLocalRasterize');
613602

614603
this._lastDrawSlot = drawSlot;
615604
this._lastNumTilesX = numTilesX;
@@ -828,10 +817,6 @@ class GSplatComputeLocalRenderer extends GSplatRenderer {
828817
});
829818
}
830819

831-
// --- Rasterize (color) ---
832-
const { shader: rasterizeColorShader, bindGroupFormat: rasterizeColorBGF } = this._createRasterizeShaderAndFormat(false);
833-
this._rasterizeColorShader = rasterizeColorShader;
834-
this._rasterizeColorBindGroupFormat = rasterizeColorBGF;
835820
}
836821

837822
/**
@@ -918,59 +903,6 @@ class GSplatComputeLocalRenderer extends GSplatRenderer {
918903
return { shader, bindGroupFormat };
919904
}
920905

921-
/**
922-
* Creates the rasterize shader + bind group format.
923-
*
924-
* @param {boolean} pickMode - Whether to create the pick variant.
925-
* @returns {{ shader: Shader, bindGroupFormat: BindGroupFormat }} The shader and format.
926-
* @private
927-
*/
928-
_createRasterizeShaderAndFormat(pickMode) {
929-
const device = this.device;
930-
931-
const ubf = new UniformBufferFormat(device, [
932-
new UniformFormat('screenWidth', UNIFORMTYPE_UINT),
933-
new UniformFormat('screenHeight', UNIFORMTYPE_UINT),
934-
new UniformFormat('numTilesX', UNIFORMTYPE_UINT),
935-
new UniformFormat('nearClip', UNIFORMTYPE_FLOAT),
936-
new UniformFormat('farClip', UNIFORMTYPE_FLOAT),
937-
new UniformFormat('alphaClip', UNIFORMTYPE_FLOAT)
938-
]);
939-
940-
const sharedBindings = [
941-
new BindUniformBufferFormat('uniforms', SHADERSTAGE_COMPUTE),
942-
new BindStorageBufferFormat('tileEntries', SHADERSTAGE_COMPUTE, true),
943-
new BindStorageBufferFormat('tileSplatCounts', SHADERSTAGE_COMPUTE, true),
944-
new BindStorageBufferFormat('projCache', SHADERSTAGE_COMPUTE, true),
945-
new BindStorageBufferFormat('rasterizeTileList', SHADERSTAGE_COMPUTE, true),
946-
new BindStorageBufferFormat('tileListCounts', SHADERSTAGE_COMPUTE, true)
947-
];
948-
949-
const outputBindings = pickMode ? [
950-
new BindStorageTextureFormat('pickIdTexture', PIXELFORMAT_R32U),
951-
new BindStorageTextureFormat('pickDepthTexture', PIXELFORMAT_RGBA16F)
952-
] : [
953-
new BindStorageTextureFormat('outputTexture', PIXELFORMAT_RGBA16F)
954-
];
955-
956-
const bgf = new BindGroupFormat(device, [...sharedBindings, ...outputBindings]);
957-
958-
const cdefines = pickMode ? new Map([['PICK_MODE', '']]) : undefined;
959-
const cincludes = pickMode ? undefined : new Map([['decodePS', shaderChunksWGSL.decodePS]]);
960-
961-
const shader = new Shader(device, {
962-
name: pickMode ? 'GSplatLocalRasterizePick' : 'GSplatLocalRasterize',
963-
shaderLanguage: SHADERLANGUAGE_WGSL,
964-
cshader: computeGsplatLocalRasterizeSource,
965-
cdefines,
966-
cincludes,
967-
computeBindGroupFormat: bgf,
968-
computeUniformBufferFormats: { uniforms: ubf }
969-
});
970-
971-
return { shader, bindGroupFormat: bgf };
972-
}
973-
974906
/**
975907
* Creates a dispatch set with its 8 Compute instances.
976908
*
@@ -1002,18 +934,6 @@ class GSplatComputeLocalRenderer extends GSplatRenderer {
1002934
// ChunkSort: shared shader
1003935
set.chunkSortCompute = new Compute(device, this._chunkSortShader, pickMode ? 'GSplatPickChunkSort' : 'GSplatLocalChunkSort');
1004936

1005-
// Rasterize: mode-specific shader and BGF
1006-
if (pickMode) {
1007-
if (!this._rasterizePickShader) {
1008-
const { shader, bindGroupFormat } = this._createRasterizeShaderAndFormat(true);
1009-
this._rasterizePickShader = shader;
1010-
set.rasterizeBindGroupFormat = bindGroupFormat;
1011-
}
1012-
set.rasterizeCompute = new Compute(device, this._rasterizePickShader, 'GSplatPickRasterize');
1013-
} else {
1014-
set.rasterizeCompute = new Compute(device, this._rasterizeColorShader, 'GSplatLocalRasterize');
1015-
}
1016-
1017937
return set;
1018938
}
1019939
}

0 commit comments

Comments
 (0)