Skip to content

Commit

Permalink
Updated the way WebGPU dynamic UBs are used for rendering (#6349)
Browse files Browse the repository at this point in the history
* Updated the way WebGPU dynamic UBs are used for rendering

* cleanup

---------

Co-authored-by: Martin Valigursky <mvaligursky@snapchat.com>
  • Loading branch information
mvaligursky and Martin Valigursky committed May 13, 2024
1 parent 374aef1 commit e1d77b9
Show file tree
Hide file tree
Showing 13 changed files with 117 additions and 70 deletions.
8 changes: 2 additions & 6 deletions examples/src/examples/compute/particles.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -220,18 +220,14 @@ assetListLoader.load(() => {
new pc.UniformFormat('matrix_model', pc.UNIFORMTYPE_MAT4)
]),
meshBindGroupFormat: new pc.BindGroupFormat(app.graphicsDevice, [
// uniforms
new pc.BindUniformBufferFormat(
pc.UNIFORM_BUFFER_DEFAULT_SLOT_NAME,
pc.SHADERSTAGE_VERTEX | pc.SHADERSTAGE_FRAGMENT
),
// particle storage buffer in read-only mode
new pc.BindStorageBufferFormat('particles', pc.SHADERSTAGE_VERTEX | pc.SHADERSTAGE_FRAGMENT, true)
])
};

// material to render the particles
const material = new pc.Material();
material.name = 'ParticleRenderingMaterial';
material.shader = new pc.Shader(app.graphicsDevice, shaderDefinition);

// index buffer - two triangles (6 indices) per particle using 4 vertices
Expand All @@ -254,7 +250,7 @@ assetListLoader.load(() => {
const meshInstance = new pc.MeshInstance(mesh, material);
meshInstance.cull = false; // disable culling as we did not supply custom aabb for the mesh instance

const entity = new pc.Entity();
const entity = new pc.Entity('ParticleRenderingEntity');
entity.addComponent('render', {
meshInstances: [meshInstance]
});
Expand Down
6 changes: 3 additions & 3 deletions examples/src/examples/compute/particles.shader-rendering.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ struct ub_view {
matrix_viewProjection : mat4x4f
}

@group(0) @binding(0) var<uniform> uvMesh : ub_mesh;
@group(0) @binding(1) var<storage, read> particles: array<Particle>;
@group(1) @binding(0) var<uniform> ubView : ub_view;
@group(2) @binding(0) var<uniform> ubMesh : ub_mesh;
@group(1) @binding(0) var<storage, read> particles: array<Particle>;
@group(0) @binding(0) var<uniform> ubView : ub_view;

// quad vertices - used to expand the particles into quads
var<private> pos : array<vec2f, 4> = array<vec2f, 4>(
Expand Down
7 changes: 1 addition & 6 deletions examples/src/examples/graphics/wgsl-shader.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,7 @@ const shaderDefinition = {
new pc.UniformFormat('matrix_model', pc.UNIFORMTYPE_MAT4),
new pc.UniformFormat('amount', pc.UNIFORMTYPE_FLOAT)
]),
meshBindGroupFormat: new pc.BindGroupFormat(app.graphicsDevice, [
new pc.BindUniformBufferFormat(
pc.UNIFORM_BUFFER_DEFAULT_SLOT_NAME,
pc.SHADERSTAGE_VERTEX | pc.SHADERSTAGE_FRAGMENT
)
])
meshBindGroupFormat: new pc.BindGroupFormat(app.graphicsDevice, [])
};
const shader = new pc.Shader(app.graphicsDevice, shaderDefinition);

Expand Down
4 changes: 2 additions & 2 deletions examples/src/examples/graphics/wgsl-shader.shader.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ struct VertexOutput {
@location(0) fragPosition: vec4f,
}

@group(0) @binding(0) var<uniform> uvMesh : ub_mesh;
@group(1) @binding(0) var<uniform> ubView : ub_view;
@group(2) @binding(0) var<uniform> uvMesh : ub_mesh;
@group(0) @binding(0) var<uniform> ubView : ub_view;

@vertex
fn vertexMain(@location(0) position : vec4f) -> VertexOutput {
Expand Down
10 changes: 5 additions & 5 deletions src/platform/graphics/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -2054,13 +2054,13 @@ export const SHADERSTAGE_FRAGMENT = 2;
*/
export const SHADERSTAGE_COMPUTE = 4;

// indices of commonly used bind groups
// sorted in a way that any trailing bind groups can be unused in any render pass
export const BINDGROUP_MESH = 0;
export const BINDGROUP_VIEW = 1;
// indices of commonly used bind groups, sorted from the least commonly changing to avoid internal rebinding
export const BINDGROUP_VIEW = 0; // view bind group, textures, samplers and uniforms
export const BINDGROUP_MESH = 1; // mesh bind group - textures and samplers
export const BINDGROUP_MESH_UB = 2; // mesh bind group - a single uniform buffer

// names of bind groups
export const bindGroupNames = ['mesh', 'view'];
export const bindGroupNames = ['view', 'mesh', 'mesh_ub'];

// name of the default uniform buffer slot in a bind group
export const UNIFORM_BUFFER_DEFAULT_SLOT_NAME = 'default';
Expand Down
19 changes: 6 additions & 13 deletions src/platform/graphics/shader-processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { Debug } from '../../core/debug.js';
import {
BINDGROUP_MESH, uniformTypeToName, semanticToLocation,
SHADERSTAGE_VERTEX, SHADERSTAGE_FRAGMENT,
UNIFORM_BUFFER_DEFAULT_SLOT_NAME,
SAMPLETYPE_FLOAT, SAMPLETYPE_DEPTH, SAMPLETYPE_UNFILTERABLE_FLOAT,
TEXTUREDIMENSION_2D, TEXTUREDIMENSION_2D_ARRAY, TEXTUREDIMENSION_CUBE, TEXTUREDIMENSION_3D,
TYPE_FLOAT32, TYPE_INT8, TYPE_INT16, TYPE_INT32, TYPE_FLOAT16, SAMPLETYPE_INT, SAMPLETYPE_UINT
TYPE_FLOAT32, TYPE_INT8, TYPE_INT16, TYPE_INT32, TYPE_FLOAT16, SAMPLETYPE_INT, SAMPLETYPE_UINT,
BINDGROUP_MESH_UB
} from './constants.js';
import { UniformFormat, UniformBufferFormat } from './uniform-buffer-format.js';
import { BindGroupFormat, BindUniformBufferFormat, BindTextureFormat } from './bind-group-format.js';
import { BindGroupFormat, BindTextureFormat } from './bind-group-format.js';

// accepted keywords
// TODO: 'out' keyword is not in the list, as handling it is more complicated due
Expand Down Expand Up @@ -269,14 +269,7 @@ class ShaderProcessor {
});
const meshUniformBufferFormat = meshUniforms.length ? new UniformBufferFormat(device, meshUniforms) : null;

// build mesh bind group format - start with uniform buffer
const uniformBufferFormats = [];
if (meshUniformBufferFormat) {
// TODO: we could optimize visibility to only stages that use any of the data
uniformBufferFormats.push(new BindUniformBufferFormat(UNIFORM_BUFFER_DEFAULT_SLOT_NAME, SHADERSTAGE_VERTEX | SHADERSTAGE_FRAGMENT));
}

// add textures uniforms
// build mesh bind group format - this contains the textures, but not the uniform buffer as that is a separate binding
const textureFormats = [];
uniformLinesSamplers.forEach((uniform) => {
// unmatched texture uniforms go to mesh block
Expand Down Expand Up @@ -307,7 +300,7 @@ class ShaderProcessor {
// validate types in else

});
const meshBindGroupFormat = new BindGroupFormat(device, [...uniformBufferFormats, ...textureFormats]);
const meshBindGroupFormat = new BindGroupFormat(device, textureFormats);

// generate code for uniform buffers
let code = '';
Expand All @@ -319,7 +312,7 @@ class ShaderProcessor {

// and also for generated mesh format, which is at the slot 0 of the bind group
if (meshUniformBufferFormat) {
code += meshUniformBufferFormat.getShaderDeclaration(BINDGROUP_MESH, 0);
code += meshUniformBufferFormat.getShaderDeclaration(BINDGROUP_MESH_UB, 0);
}

// generate code for textures
Expand Down
13 changes: 8 additions & 5 deletions src/platform/graphics/webgpu/webgpu-clear-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { BlendState } from "../blend-state.js";
import {
CULLFACE_NONE,
PRIMITIVE_TRISTRIP, SHADERLANGUAGE_WGSL,
UNIFORMTYPE_FLOAT, UNIFORMTYPE_VEC4, BINDGROUP_MESH, CLEARFLAG_COLOR, CLEARFLAG_DEPTH, CLEARFLAG_STENCIL
UNIFORMTYPE_FLOAT, UNIFORMTYPE_VEC4, BINDGROUP_MESH, CLEARFLAG_COLOR, CLEARFLAG_DEPTH, CLEARFLAG_STENCIL,
BINDGROUP_MESH_UB
} from "../constants.js";
import { Shader } from "../shader.js";
import { DynamicBindGroup } from "../bind-group.js";
Expand Down Expand Up @@ -39,7 +40,7 @@ class WebgpuClearRenderer {
depth: f32
}
@group(0) @binding(0) var<uniform> ubMesh : ub_mesh;
@group(2) @binding(0) var<uniform> ubMesh : ub_mesh;
var<private> pos : array<vec2f, 4> = array<vec2f, 4>(
vec2(-1.0, 1.0), vec2(1.0, 1.0),
Expand Down Expand Up @@ -98,10 +99,13 @@ class WebgpuClearRenderer {

DebugGraphics.pushGpuMarker(device, 'CLEAR-RENDERER');

// dynamic bind group for this UB
// dynamic bind group for the UB
const { uniformBuffer, dynamicBindGroup } = this;
uniformBuffer.startUpdate(dynamicBindGroup);
device.setBindGroup(BINDGROUP_MESH, dynamicBindGroup.bindGroup, dynamicBindGroup.offsets);
device.setBindGroup(BINDGROUP_MESH_UB, dynamicBindGroup.bindGroup, dynamicBindGroup.offsets);

// not using mesh bind group
device.setBindGroup(BINDGROUP_MESH, device.emptyBindGroup);

// setup clear color
if ((flags & CLEARFLAG_COLOR) && (renderTarget.colorBuffer || renderTarget.impl.assignedColorTexture)) {
Expand Down Expand Up @@ -136,7 +140,6 @@ class WebgpuClearRenderer {

// render 4 vertices without vertex buffer
device.setShader(this.shader);

device.draw(primitive);

DebugGraphics.popGpuMarker(device);
Expand Down
14 changes: 14 additions & 0 deletions src/platform/graphics/webgpu/webgpu-graphics-device.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import { WebgpuGpuProfiler } from './webgpu-gpu-profiler.js';
import { WebgpuResolver } from './webgpu-resolver.js';
import { WebgpuCompute } from './webgpu-compute.js';
import { WebgpuBuffer } from './webgpu-buffer.js';
import { BindGroupFormat } from '../bind-group-format.js';
import { BindGroup } from '../bind-group.js';

const _uniqueLocations = new Map();

Expand Down Expand Up @@ -72,6 +74,14 @@ class WebgpuGraphicsDevice extends GraphicsDevice {
*/
bindGroupFormats = [];

/**
* An empty bind group, used when the draw call is using a typical bind group layout based on
* BINDGROUP_*** constants but some bind groups are not needed, for example clear renderer.
*
* @type {BindGroup}
*/
emptyBindGroup;

/**
* Current command buffer encoder.
*
Expand Down Expand Up @@ -310,6 +320,10 @@ class WebgpuGraphicsDevice extends GraphicsDevice {

// init dynamic buffer using 1MB allocation
this.dynamicBuffers = new WebgpuDynamicBuffers(this, 1024 * 1024, this.limits.minUniformBufferOffsetAlignment);

// empty bind group
this.emptyBindGroup = new BindGroup(this, new BindGroupFormat(this, []));
this.emptyBindGroup.update();
}

createBackbuffer() {
Expand Down
7 changes: 7 additions & 0 deletions src/platform/graphics/webgpu/webgpu-render-pipeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { WebgpuVertexBufferLayout } from "./webgpu-vertex-buffer-layout.js";
import { WebgpuDebug } from "./webgpu-debug.js";
import { WebgpuPipeline } from "./webgpu-pipeline.js";
import { DebugGraphics } from "../debug-graphics.js";
import { bindGroupNames } from "../constants.js";

let _pipelineId = 0;

Expand Down Expand Up @@ -120,6 +121,12 @@ class WebgpuRenderPipeline extends WebgpuPipeline {

Debug.assert(bindGroupFormats.length <= 3);

// all bind groups must be set as the WebGPU layout cannot have skipped indices. Not having a bind
// group would assign incorrect slots to the following bind groups, causing a validation errors.
Debug.assert(bindGroupFormats[0], `BindGroup with index 0 [${bindGroupNames[0]}] is not set.`);
Debug.assert(bindGroupFormats[1], `BindGroup with index 1 [${bindGroupNames[1]}] is not set.`);
Debug.assert(bindGroupFormats[2], `BindGroup with index 2 [${bindGroupNames[2]}] is not set.`);

// render pipeline unique hash
const lookupHashes = this.lookupHashes;
lookupHashes[0] = primitive.type;
Expand Down
21 changes: 17 additions & 4 deletions src/scene/graphics/quad-render.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Debug, DebugHelper } from "../../core/debug.js";
import { Vec4 } from "../../core/math/vec4.js";
import { BindGroup } from "../../platform/graphics/bind-group.js";
import { BINDGROUP_MESH, PRIMITIVE_TRISTRIP } from "../../platform/graphics/constants.js";
import { BindGroup, DynamicBindGroup } from "../../platform/graphics/bind-group.js";
import { BINDGROUP_MESH, BINDGROUP_MESH_UB, BINDGROUP_VIEW, PRIMITIVE_TRISTRIP } from "../../platform/graphics/constants.js";
import { DebugGraphics } from "../../platform/graphics/debug-graphics.js";
import { ShaderProcessorOptions } from "../../platform/graphics/shader-processor-options.js";
import { UniformBuffer } from "../../platform/graphics/uniform-buffer.js";
Expand All @@ -16,6 +16,7 @@ const _quadPrimitive = {

const _tempViewport = new Vec4();
const _tempScissor = new Vec4();
const _dynamicBindGroup = new DynamicBindGroup();

/**
* An object that renders a quad using a {@link Shader}.
Expand Down Expand Up @@ -70,7 +71,7 @@ class QuadRender {
// bind group
const bindGroupFormat = this.shader.meshBindGroupFormat;
Debug.assert(bindGroupFormat);
this.bindGroup = new BindGroup(device, bindGroupFormat, this.uniformBuffer);
this.bindGroup = new BindGroup(device, bindGroupFormat);
DebugHelper.setName(this.bindGroup, `QuadRender-MeshBindGroup_${this.bindGroup.id}`);
}
}
Expand Down Expand Up @@ -120,10 +121,22 @@ class QuadRender {

if (device.supportsUniformBuffers) {

// not using view bind group
device.setBindGroup(BINDGROUP_VIEW, device.emptyBindGroup);

// mesh bind group
const bindGroup = this.bindGroup;
bindGroup.defaultUniformBuffer?.update();
bindGroup.update();
device.setBindGroup(BINDGROUP_MESH, bindGroup);

// dynamic uniform buffer bind group
const uniformBuffer = this.uniformBuffer;
if (uniformBuffer) {
uniformBuffer.update(_dynamicBindGroup);
device.setBindGroup(BINDGROUP_MESH_UB, _dynamicBindGroup.bindGroup, _dynamicBindGroup.offsets);
} else {
device.setBindGroup(BINDGROUP_MESH_UB, device.emptyBindGroup);
}
}

device.draw(_quadPrimitive);
Expand Down
50 changes: 36 additions & 14 deletions src/scene/mesh-instance.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,19 @@ class ShaderInstance {
shader;

/**
* A bind group storing mesh uniforms for the shader.
* A bind group storing mesh textures / samplers for the shader. but not the uniform buffer.
*
* @type {BindGroup|null}
*/
bindGroup = null;

/**
* A uniform buffer storing mesh uniforms for the shader.
*
* @type {UniformBuffer|null}
*/
uniformBuffer = null;

/**
* Returns the mesh bind group for the shader.
*
Expand All @@ -79,28 +86,43 @@ class ShaderInstance {
const shader = this.shader;
Debug.assert(shader);

// mesh uniform buffer
const ubFormat = shader.meshUniformBufferFormat;
Debug.assert(ubFormat);
const uniformBuffer = new UniformBuffer(device, ubFormat, false);

// mesh bind group
const bindGroupFormat = shader.meshBindGroupFormat;
Debug.assert(bindGroupFormat);
this.bindGroup = new BindGroup(device, bindGroupFormat, uniformBuffer);
this.bindGroup = new BindGroup(device, bindGroupFormat);
DebugHelper.setName(this.bindGroup, `MeshBindGroup_${this.bindGroup.id}`);
}

return this.bindGroup;
}

destroy() {
const group = this.bindGroup;
if (group) {
group.defaultUniformBuffer?.destroy();
group.destroy();
this.bindGroup = null;
/**
* Returns the uniform buffer for the shader.
*
* @param {import('../platform/graphics/graphics-device.js').GraphicsDevice} device - The
* graphics device.
* @returns {UniformBuffer} - The uniform buffer.
*/
getUniformBuffer(device) {

// create uniform buffer
if (!this.uniformBuffer) {
const shader = this.shader;
Debug.assert(shader);

const ubFormat = shader.meshUniformBufferFormat;
Debug.assert(ubFormat);
this.uniformBuffer = new UniformBuffer(device, ubFormat, false);
}

return this.uniformBuffer;
}

destroy() {
this.bindGroup?.destroy();
this.bindGroup = null;

this.uniformBuffer?.destroy();
this.uniformBuffer = null;
}
}

Expand Down
16 changes: 8 additions & 8 deletions src/scene/renderer/forward-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -685,14 +685,6 @@ class ForwardRenderer extends Renderer {

this.setupViewport(camera, renderTarget);

// clearing
const clearColor = options.clearColor ?? false;
const clearDepth = options.clearDepth ?? false;
const clearStencil = options.clearStencil ?? false;
if (clearColor || clearDepth || clearStencil) {
this.clear(camera, clearColor, clearDepth, clearStencil);
}

let visible, splitLights;
if (layer) {
// #if _PROFILER
Expand Down Expand Up @@ -748,6 +740,14 @@ class ForwardRenderer extends Renderer {
this.setupViewUniformBuffers(viewBindGroups, this.viewUniformFormat, this.viewBindGroupFormat, viewCount);
}

// clearing - do it after the view bind groups are set up, to avoid overriding those
const clearColor = options.clearColor ?? false;
const clearDepth = options.clearDepth ?? false;
const clearStencil = options.clearStencil ?? false;
if (clearColor || clearDepth || clearStencil) {
this.clear(camera, clearColor, clearDepth, clearStencil);
}

// enable flip faces if either the camera has _flipFaces enabled or the render target has flipY enabled
const flipFaces = !!(camera._flipFaces ^ renderTarget?.flipY);

Expand Down

0 comments on commit e1d77b9

Please sign in to comment.