Skip to content

Commit

Permalink
Initial refactor to how bind groups can be used with non-persistent U…
Browse files Browse the repository at this point in the history
…Bs (#6341)

* Initial refactor to how bind groups can be used with non-persistent UBs

* types fix

* reverted a small thing

---------

Co-authored-by: Martin Valigursky <[email protected]>
  • Loading branch information
mvaligursky and Martin Valigursky authored May 7, 2024
1 parent 9b9285f commit 95ddb6c
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 66 deletions.
14 changes: 13 additions & 1 deletion src/platform/graphics/bind-group.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ import { DebugGraphics } from './debug-graphics.js';

let id = 0;

/**
* Data structure to hold a bind group and its offsets. This is used by {@link UniformBuffer#update}
* to return a dynamic bind group and offset for the uniform buffer.
*
* @ignore
*/
class DynamicBindGroup {
bindGroup;

offsets = [];
}

/**
* A bind group represents a collection of {@link UniformBuffer}, {@link Texture} and
* {@link StorageBuffer} instanced, which can be bind on a GPU for rendering.
Expand Down Expand Up @@ -201,4 +213,4 @@ class BindGroup {
}
}

export { BindGroup };
export { BindGroup, DynamicBindGroup };
50 changes: 50 additions & 0 deletions src/platform/graphics/dynamic-buffer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { DebugHelper } from '../../core/debug.js';
import { BindGroupFormat, BindUniformBufferFormat } from './bind-group-format.js';
import { BindGroup } from './bind-group.js';
import { SHADERSTAGE_FRAGMENT, SHADERSTAGE_VERTEX, UNIFORM_BUFFER_DEFAULT_SLOT_NAME } from './constants.js';

/**
* A base class representing a single per platform buffer.
*
* @ignore
*/
class DynamicBuffer {
/** @type {import('./graphics-device.js').GraphicsDevice} */
device;

/**
* A cache of bind groups for each uniform buffer size, which is used to avoid creating a new
* bind group for each uniform buffer.
*
* @type {Map<number, BindGroup>}
*/
bindGroupCache = new Map();

constructor(device) {
this.device = device;

// format of the bind group
this.bindGroupFormat = new BindGroupFormat(this.device, [
new BindUniformBufferFormat(UNIFORM_BUFFER_DEFAULT_SLOT_NAME, SHADERSTAGE_VERTEX | SHADERSTAGE_FRAGMENT)
]);
}

getBindGroup(ub) {
const ubSize = ub.format.byteSize;
let bindGroup = this.bindGroupCache.get(ubSize);
if (!bindGroup) {

// bind group
// we pass ub to it, but internally only its size is used
bindGroup = new BindGroup(this.device, this.bindGroupFormat, ub);
DebugHelper.setName(bindGroup, `DynamicBuffer-BindGroup_${bindGroup.id}-${ubSize}`);
bindGroup.update();

this.bindGroupCache.set(ubSize, bindGroup);
}

return bindGroup;
}
}

export { DynamicBuffer };
26 changes: 6 additions & 20 deletions src/platform/graphics/dynamic-buffers.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,16 @@
import { Debug } from '../../core/debug.js';
import { math } from '../../core/math/math.js';

/**
* A base class representing a single per platform buffer.
*
* @ignore
*/
class DynamicBuffer {
/** @type {import('./graphics-device.js').GraphicsDevice} */
device;

constructor(device) {
this.device = device;
}
}

/**
* A container for storing the used areas of a pair of staging and gpu buffers.
*
* @ignore
*/
class UsedBuffer {
/** @type {DynamicBuffer} */
/** @type {import('./dynamic-buffer.js').DynamicBuffer} */
gpuBuffer;

/** @type {DynamicBuffer} */
/** @type {import('./dynamic-buffer.js').DynamicBuffer} */
stagingBuffer;

/**
Expand Down Expand Up @@ -59,7 +45,7 @@ class DynamicBufferAllocation {
/**
* The gpu buffer this allocation will be copied to.
*
* @type {DynamicBuffer}
* @type {import('./dynamic-buffer.js').DynamicBuffer}
*/
gpuBuffer;

Expand Down Expand Up @@ -93,14 +79,14 @@ class DynamicBuffers {
/**
* Internally allocated gpu buffers.
*
* @type {DynamicBuffer[]}
* @type {import('./dynamic-buffer.js').DynamicBuffer[]}
*/
gpuBuffers = [];

/**
* Internally allocated staging buffers (CPU writable)
*
* @type {DynamicBuffer[]}
* @type {import('./dynamic-buffer.js').DynamicBuffer[]}
*/
stagingBuffers = [];

Expand Down Expand Up @@ -215,4 +201,4 @@ class DynamicBuffers {
}
}

export { DynamicBuffer, DynamicBuffers, DynamicBufferAllocation };
export { DynamicBuffers, DynamicBufferAllocation };
50 changes: 37 additions & 13 deletions src/platform/graphics/uniform-buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,11 +304,11 @@ class UniformBuffer {
*
* @param {import('./uniform-buffer-format.js').UniformFormat} uniformFormat - The format of
* the uniform.
* @param {any} value - The value to assign to the uniform.
*/
setUniform(uniformFormat) {
setUniform(uniformFormat, value) {
Debug.assert(uniformFormat);
const offset = uniformFormat.offset;
const value = uniformFormat.scopeId.value;

if (value !== null && value !== undefined) {

Expand All @@ -328,46 +328,70 @@ class UniformBuffer {
* Assign a value to the uniform specified by name.
*
* @param {string} name - The name of the uniform.
* @param {any} value - The value to assign to the uniform.
*/
set(name) {
set(name, value) {
const uniformFormat = this.format.map.get(name);
Debug.assert(uniformFormat, `Uniform name [${name}] is not part of the Uniform buffer.`);
if (uniformFormat) {
this.setUniform(uniformFormat);
this.setUniform(uniformFormat, value);
}
}

update() {
startUpdate(dynamicBindGroup) {

const persistent = this.persistent;
if (!persistent) {
if (!this.persistent) {

// allocate memory from dynamic buffer for this frame
const allocation = this.allocation;
const oldGpuBuffer = allocation.gpuBuffer;
this.device.dynamicBuffers.alloc(allocation, this.format.byteSize);
this.assignStorage(allocation.storage);

// get info about bind group we can use for this non-persistent UB for this frame
if (dynamicBindGroup) {
dynamicBindGroup.bindGroup = allocation.gpuBuffer.getBindGroup(this);
dynamicBindGroup.offsets[0] = allocation.offset;
}

// buffer has changed, update the render version to force bind group to be updated
if (oldGpuBuffer !== allocation.gpuBuffer) {
this.renderVersionDirty = this.device.renderVersion;
}
}
}

// set new values
const uniforms = this.format.uniforms;
for (let i = 0; i < uniforms.length; i++) {
this.setUniform(uniforms[i]);
}
endUpdate() {

if (persistent) {
if (this.persistent) {
// Upload the new data
this.impl.unlock(this);
} else {
this.storageFloat32 = null;
this.storageInt32 = null;
}
}

/**
* @param {import('./bind-group.js').DynamicBindGroup} [dynamicBindGroup] - The function fills
* in the info about the dynamic bind group for this frame, which uses this uniform buffer. Only
* used if the uniform buffer is non-persistent. This allows the uniform buffer to be used
* without having to create a bind group for it. Note that the bind group can only contains
* this single uniform buffer, and no other resources.
*/
update(dynamicBindGroup) {

this.startUpdate(dynamicBindGroup);

// set new values
const uniforms = this.format.uniforms;
for (let i = 0; i < uniforms.length; i++) {
const value = uniforms[i].scopeId.value;
this.setUniform(uniforms[i], value);
}

this.endUpdate();
}
}

export { UniformBuffer };
5 changes: 2 additions & 3 deletions src/platform/graphics/webgpu/webgpu-bind-group.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ class WebgpuBindGroup {
}

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

Expand All @@ -44,8 +43,8 @@ class WebgpuBindGroup {
* @param {import('./webgpu-graphics-device.js').WebgpuGraphicsDevice} device - Graphics device.
* @param {import('../bind-group.js').BindGroup} bindGroup - Bind group to create the
* descriptor for.
* @returns {object} - Returns the generated descriptor of type
* GPUBindGroupDescriptor, which can be used to create a GPUBindGroup
* @returns {object} - Returns the generated descriptor of type GPUBindGroupDescriptor, which
* can be used to create a GPUBindGroup
*/
createDescriptor(device, bindGroup) {

Expand Down
41 changes: 15 additions & 26 deletions src/platform/graphics/webgpu/webgpu-clear-renderer.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { Debug, DebugHelper } from "../../../core/debug.js";
import { BindUniformBufferFormat, BindGroupFormat } from "../bind-group-format.js";
import { Debug } from "../../../core/debug.js";
import { UniformBufferFormat, UniformFormat } from "../uniform-buffer-format.js";
import { BlendState } from "../blend-state.js";
import {
CULLFACE_NONE,
PRIMITIVE_TRISTRIP, SHADERLANGUAGE_WGSL, SHADERSTAGE_FRAGMENT, SHADERSTAGE_VERTEX,
UNIFORMTYPE_FLOAT, UNIFORMTYPE_VEC4, UNIFORM_BUFFER_DEFAULT_SLOT_NAME, BINDGROUP_MESH, CLEARFLAG_COLOR, CLEARFLAG_DEPTH, CLEARFLAG_STENCIL
PRIMITIVE_TRISTRIP, SHADERLANGUAGE_WGSL,
UNIFORMTYPE_FLOAT, UNIFORMTYPE_VEC4, BINDGROUP_MESH, CLEARFLAG_COLOR, CLEARFLAG_DEPTH, CLEARFLAG_STENCIL
} from "../constants.js";
import { Shader } from "../shader.js";
import { BindGroup } from "../bind-group.js";
import { DynamicBindGroup } from "../bind-group.js";
import { UniformBuffer } from "../uniform-buffer.js";
import { DebugGraphics } from "../debug-graphics.js";
import { DepthState } from "../depth-state.js";
Expand Down Expand Up @@ -77,19 +76,10 @@ class WebgpuClearRenderer {
new UniformFormat('depth', UNIFORMTYPE_FLOAT)
]), false);

// format of the bind group
const bindGroupFormat = new BindGroupFormat(device, [
new BindUniformBufferFormat(UNIFORM_BUFFER_DEFAULT_SLOT_NAME, SHADERSTAGE_VERTEX | SHADERSTAGE_FRAGMENT)
]);

// bind group
this.bindGroup = new BindGroup(device, bindGroupFormat, this.uniformBuffer);
DebugHelper.setName(this.bindGroup, `ClearRenderer-BindGroup_${this.bindGroup.id}`);
this.dynamicBindGroup = new DynamicBindGroup();

// uniform data
this.colorData = new Float32Array(4);
this.colorId = device.scope.resolve('color');
this.depthId = device.scope.resolve('depth');
}

destroy() {
Expand All @@ -98,9 +88,6 @@ class WebgpuClearRenderer {

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

this.bindGroup.destroy();
this.bindGroup = null;
}

clear(device, renderTarget, options, defaultOptions) {
Expand All @@ -111,6 +98,11 @@ class WebgpuClearRenderer {

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

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

// setup clear color
if ((flags & CLEARFLAG_COLOR) && (renderTarget.colorBuffer || renderTarget.impl.assignedColorTexture)) {
const color = options.color ?? defaultOptions.color;
Expand All @@ -120,16 +112,16 @@ class WebgpuClearRenderer {
} else {
device.setBlendState(BlendState.NOWRITE);
}
this.colorId.setValue(this.colorData);
uniformBuffer.set('color', this.colorData);

// setup depth clear
if ((flags & CLEARFLAG_DEPTH) && renderTarget.depth) {
const depth = options.depth ?? defaultOptions.depth;
this.depthId.setValue(depth);
uniformBuffer.set('depth', depth);
device.setDepthState(DepthState.WRITEDEPTH);

} else {
this.depthId.setValue(1);
uniformBuffer.set('depth', 1);
device.setDepthState(DepthState.NODEPTH);
}

Expand All @@ -138,16 +130,13 @@ class WebgpuClearRenderer {
Debug.warnOnce("ClearRenderer does not support stencil clear at the moment");
}

uniformBuffer.endUpdate();

device.setCullMode(CULLFACE_NONE);

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

const bindGroup = this.bindGroup;
bindGroup.defaultUniformBuffer.update();
bindGroup.update();
device.setBindGroup(BINDGROUP_MESH, bindGroup);

device.draw(primitive);

DebugGraphics.popGpuMarker(device);
Expand Down
2 changes: 1 addition & 1 deletion src/platform/graphics/webgpu/webgpu-dynamic-buffer.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DebugHelper } from "../../../core/debug.js";
import { DynamicBuffer } from "../dynamic-buffers.js";
import { DynamicBuffer } from "../dynamic-buffer.js";

/**
* @ignore
Expand Down
5 changes: 3 additions & 2 deletions src/platform/graphics/webgpu/webgpu-graphics-device.js
Original file line number Diff line number Diff line change
Expand Up @@ -419,14 +419,15 @@ class WebgpuGraphicsDevice extends GraphicsDevice {
/**
* @param {number} index - Index of the bind group slot
* @param {import('../bind-group.js').BindGroup} bindGroup - Bind group to attach
* @param {number[]} [offsets] - Byte offsets for all uniform buffers in the bind group.
*/
setBindGroup(index, bindGroup) {
setBindGroup(index, bindGroup, offsets) {

// TODO: this condition should be removed, it's here to handle fake grab pass, which should be refactored instead
if (this.passEncoder) {

// set it on the device
this.passEncoder.setBindGroup(index, bindGroup.impl.bindGroup, bindGroup.uniformBufferOffsets);
this.passEncoder.setBindGroup(index, bindGroup.impl.bindGroup, offsets ?? bindGroup.uniformBufferOffsets);

// store the active formats, used by the pipeline creation
this.bindGroupFormats[index] = bindGroup.format.impl;
Expand Down

0 comments on commit 95ddb6c

Please sign in to comment.