diff --git a/examples/src/examples/compute/particles.example.mjs b/examples/src/examples/compute/particles.example.mjs index deb05191a66..ddb2175f7a7 100644 --- a/examples/src/examples/compute/particles.example.mjs +++ b/examples/src/examples/compute/particles.example.mjs @@ -220,11 +220,6 @@ 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) ]) @@ -232,6 +227,7 @@ assetListLoader.load(() => { // 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 @@ -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] }); diff --git a/examples/src/examples/compute/particles.shader-rendering.wgsl b/examples/src/examples/compute/particles.shader-rendering.wgsl index 4f6da161948..36c56427944 100644 --- a/examples/src/examples/compute/particles.shader-rendering.wgsl +++ b/examples/src/examples/compute/particles.shader-rendering.wgsl @@ -8,9 +8,9 @@ struct ub_view { matrix_viewProjection : mat4x4f } -@group(0) @binding(0) var uvMesh : ub_mesh; -@group(0) @binding(1) var particles: array; -@group(1) @binding(0) var ubView : ub_view; +@group(2) @binding(0) var ubMesh : ub_mesh; +@group(1) @binding(0) var particles: array; +@group(0) @binding(0) var ubView : ub_view; // quad vertices - used to expand the particles into quads var pos : array = array( diff --git a/examples/src/examples/graphics/wgsl-shader.example.mjs b/examples/src/examples/graphics/wgsl-shader.example.mjs index 9b23bb66be4..76162178aab 100644 --- a/examples/src/examples/graphics/wgsl-shader.example.mjs +++ b/examples/src/examples/graphics/wgsl-shader.example.mjs @@ -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); diff --git a/examples/src/examples/graphics/wgsl-shader.shader.wgsl b/examples/src/examples/graphics/wgsl-shader.shader.wgsl index d15612a3a15..63953a6c382 100644 --- a/examples/src/examples/graphics/wgsl-shader.shader.wgsl +++ b/examples/src/examples/graphics/wgsl-shader.shader.wgsl @@ -12,8 +12,8 @@ struct VertexOutput { @location(0) fragPosition: vec4f, } -@group(0) @binding(0) var uvMesh : ub_mesh; -@group(1) @binding(0) var ubView : ub_view; +@group(2) @binding(0) var uvMesh : ub_mesh; +@group(0) @binding(0) var ubView : ub_view; @vertex fn vertexMain(@location(0) position : vec4f) -> VertexOutput { diff --git a/src/platform/graphics/constants.js b/src/platform/graphics/constants.js index 9709b973ff3..6199cdb156b 100644 --- a/src/platform/graphics/constants.js +++ b/src/platform/graphics/constants.js @@ -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'; diff --git a/src/platform/graphics/shader-processor.js b/src/platform/graphics/shader-processor.js index 9319e7d195f..205118a53c2 100644 --- a/src/platform/graphics/shader-processor.js +++ b/src/platform/graphics/shader-processor.js @@ -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 @@ -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 @@ -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 = ''; @@ -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 diff --git a/src/platform/graphics/webgpu/webgpu-clear-renderer.js b/src/platform/graphics/webgpu/webgpu-clear-renderer.js index 176acb2ba31..6cd1c7943c5 100644 --- a/src/platform/graphics/webgpu/webgpu-clear-renderer.js +++ b/src/platform/graphics/webgpu/webgpu-clear-renderer.js @@ -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"; @@ -39,7 +40,7 @@ class WebgpuClearRenderer { depth: f32 } - @group(0) @binding(0) var ubMesh : ub_mesh; + @group(2) @binding(0) var ubMesh : ub_mesh; var pos : array = array( vec2(-1.0, 1.0), vec2(1.0, 1.0), @@ -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)) { @@ -136,7 +140,6 @@ class WebgpuClearRenderer { // render 4 vertices without vertex buffer device.setShader(this.shader); - device.draw(primitive); DebugGraphics.popGpuMarker(device); diff --git a/src/platform/graphics/webgpu/webgpu-graphics-device.js b/src/platform/graphics/webgpu/webgpu-graphics-device.js index 4214f3b6e5f..71440f7b7e3 100644 --- a/src/platform/graphics/webgpu/webgpu-graphics-device.js +++ b/src/platform/graphics/webgpu/webgpu-graphics-device.js @@ -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(); @@ -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. * @@ -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() { diff --git a/src/platform/graphics/webgpu/webgpu-render-pipeline.js b/src/platform/graphics/webgpu/webgpu-render-pipeline.js index 8ff449f8a8f..80945f1a396 100644 --- a/src/platform/graphics/webgpu/webgpu-render-pipeline.js +++ b/src/platform/graphics/webgpu/webgpu-render-pipeline.js @@ -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; @@ -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; diff --git a/src/scene/graphics/quad-render.js b/src/scene/graphics/quad-render.js index b8f363f64a3..ab7693fd873 100644 --- a/src/scene/graphics/quad-render.js +++ b/src/scene/graphics/quad-render.js @@ -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"; @@ -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}. @@ -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}`); } } @@ -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); diff --git a/src/scene/mesh-instance.js b/src/scene/mesh-instance.js index 24df3a06aad..1162c9faa2d 100644 --- a/src/scene/mesh-instance.js +++ b/src/scene/mesh-instance.js @@ -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. * @@ -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; } } diff --git a/src/scene/renderer/forward-renderer.js b/src/scene/renderer/forward-renderer.js index e41d455cf0b..d386fdad5a7 100644 --- a/src/scene/renderer/forward-renderer.js +++ b/src/scene/renderer/forward-renderer.js @@ -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 @@ -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); diff --git a/src/scene/renderer/renderer.js b/src/scene/renderer/renderer.js index eabdcc8fe6b..7f82522a910 100644 --- a/src/scene/renderer/renderer.js +++ b/src/scene/renderer/renderer.js @@ -24,11 +24,12 @@ import { SHADERSTAGE_VERTEX, SHADERSTAGE_FRAGMENT, SEMANTIC_ATTR, CULLFACE_BACK, CULLFACE_FRONT, CULLFACE_NONE, - TEXTUREDIMENSION_2D, SAMPLETYPE_UNFILTERABLE_FLOAT, SAMPLETYPE_FLOAT, SAMPLETYPE_DEPTH + TEXTUREDIMENSION_2D, SAMPLETYPE_UNFILTERABLE_FLOAT, SAMPLETYPE_FLOAT, SAMPLETYPE_DEPTH, + BINDGROUP_MESH_UB } from '../../platform/graphics/constants.js'; import { DebugGraphics } from '../../platform/graphics/debug-graphics.js'; import { UniformBuffer } from '../../platform/graphics/uniform-buffer.js'; -import { BindGroup } from '../../platform/graphics/bind-group.js'; +import { BindGroup, DynamicBindGroup } from '../../platform/graphics/bind-group.js'; import { UniformFormat, UniformBufferFormat } from '../../platform/graphics/uniform-buffer-format.js'; import { BindGroupFormat, BindUniformBufferFormat, BindTextureFormat } from '../../platform/graphics/bind-group-format.js'; @@ -50,6 +51,7 @@ const tempSphere = new BoundingSphere(); const _flipYMat = new Mat4().setScale(1, -1, 1); const _tempLightSet = new Set(); const _tempLayerSet = new Set(); +const _dynamicBindGroup = new DynamicBindGroup(); // Converts a projection matrix in OpenGL style (depth range of -1..1) to a DirectX style (depth range of 0..1). const _fixProjRangeMat = new Mat4().set([ @@ -866,10 +868,12 @@ class Renderer { // update mesh bind group / uniform buffer const meshBindGroup = shaderInstance.getBindGroup(device); - - meshBindGroup.defaultUniformBuffer.update(); meshBindGroup.update(); device.setBindGroup(BINDGROUP_MESH, meshBindGroup); + + const meshUniformBuffer = shaderInstance.getUniformBuffer(device); + meshUniformBuffer.update(_dynamicBindGroup); + device.setBindGroup(BINDGROUP_MESH_UB, _dynamicBindGroup.bindGroup, _dynamicBindGroup.offsets); } }