Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated the way WebGPU dynamic UBs are used for rendering #6349

Merged
merged 2 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading