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

Initial framework for compute shaders on WebGPU #5757

Merged
merged 2 commits into from Oct 17, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/core/constants.js
Expand Up @@ -104,6 +104,13 @@ export const TRACEID_BINDGROUPFORMAT_ALLOC = 'BindGroupFormatAlloc';
*/
export const TRACEID_RENDERPIPELINE_ALLOC = 'RenderPipelineAlloc';

/**
* Logs the creation of compute pipelines. WebGPU only.
*
* @type {string}
*/
export const TRACEID_COMPUTEPIPELINE_ALLOC = 'ComputePipelineAlloc';

/**
* Logs the creation of pipeline layouts. WebBPU only.
*
Expand Down
1 change: 1 addition & 0 deletions src/core/tracing.js
Expand Up @@ -37,6 +37,7 @@ class Tracing {
* - {@link TRACEID_VRAM_VB}
* - {@link TRACEID_VRAM_IB}
* - {@link TRACEID_RENDERPIPELINE_ALLOC}
* - {@link TRACEID_COMPUTEPIPELINE_ALLOC}
* - {@link TRACEID_PIPELINELAYOUT_ALLOC}
* - {@link TRACEID_TEXTURES}
* - {@link TRACEID_GPU_TIMINGS}
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Expand Up @@ -58,6 +58,7 @@ export * from './platform/audio/constants.js';
export * from './platform/graphics/constants.js';
export { createGraphicsDevice } from './platform/graphics/graphics-device-create.js';
export { BlendState } from './platform/graphics/blend-state.js';
export { Compute } from './platform/graphics/compute.js';
export { DepthState } from './platform/graphics/depth-state.js';
export { GraphicsDevice } from './platform/graphics/graphics-device.js';
export { IndexBuffer } from './platform/graphics/index-buffer.js';
Expand Down
44 changes: 44 additions & 0 deletions src/platform/graphics/compute.js
@@ -0,0 +1,44 @@
/**
* A representation of a compute shader with the associated data, that can be executed on the GPU.
*
* @ignore
*/
class Compute {
/**
* A compute shader.
*
* @type {import('./shader.js').Shader|null}
* @ignore
*/
shader = null;

/**
* Create a compute instance. Note that this is supported on WebGPU only and is a no-op on
* other platforms.
*
* @param {import('./graphics-device.js').GraphicsDevice} graphicsDevice -
* The graphics device.
* @param {import('./shader.js').Shader} shader - The compute shader.
*/
constructor(graphicsDevice, shader) {
this.device = graphicsDevice;
this.shader = shader;

if (graphicsDevice.supportsCompute) {
this.impl = graphicsDevice.createComputeImpl(this);
}
}

/**
* Dispatch the compute work.
*
* @param {number} x - X dimension of the grid of work-groups to dispatch.
* @param {number} [y] - Y dimension of the grid of work-groups to dispatch.
* @param {number} [z] - Z dimension of the grid of work-groups to dispatch.
*/
dispatch(x, y, z) {
this.impl?.dispatch(x, y, z);
}
}

export { Compute };
20 changes: 20 additions & 0 deletions src/platform/graphics/graphics-device.js
Expand Up @@ -187,6 +187,14 @@ class GraphicsDevice extends EventHandler {
*/
supportsVolumeTextures = false;

/**
* True if the device supports compute shaders.
*
* @readonly
* @type {boolean}
*/
supportsCompute = false;

/**
* Currently active render target.
*
Expand Down Expand Up @@ -753,6 +761,18 @@ class GraphicsDevice extends EventHandler {
this.boneLimit = maxBones;
}

startRenderPass(renderPass) {
}

endRenderPass(renderPass) {
}

startComputePass() {
}

endComputePass() {
}

/**
* Function which executes at the start of the frame. This should not be called manually, as
* it is handled by the AppBase instance.
Expand Down
6 changes: 0 additions & 6 deletions src/platform/graphics/null/null-graphics-device.js
Expand Up @@ -123,12 +123,6 @@ class NullGraphicsDevice extends GraphicsDevice {
super.initializeContextCaches();
}

startPass(renderPass) {
}

endPass(renderPass) {
}

clear(options) {
}

Expand Down
4 changes: 2 additions & 2 deletions src/platform/graphics/render-pass.js
Expand Up @@ -286,13 +286,13 @@ class RenderPass {
if (this.executeEnabled) {

if (realPass) {
device.startPass(this);
device.startRenderPass(this);
}

this.execute();

if (realPass) {
device.endPass(this);
device.endRenderPass(this);
}
}

Expand Down
25 changes: 16 additions & 9 deletions src/platform/graphics/shader.js
Expand Up @@ -46,9 +46,12 @@ class Shader {
* vertex shader attribute names to semantics SEMANTIC_*. This enables the engine to match
* vertex buffer data as inputs to the shader. When not specified, rendering without
* vertex buffer is assumed.
* @param {string} definition.vshader - Vertex shader source (GLSL code).
* @param {string} [definition.vshader] - Vertex shader source (GLSL code). Optional when
* compute shader is specified.
* @param {string} [definition.fshader] - Fragment shader source (GLSL code). Optional when
* useTransformFeedback is specified.
* useTransformFeedback or compute shader is specified.
* @param {string} [definition.cshader] - Compute shader source (WGSL code). Only supported on
* WebGPU platform.
* @param {boolean} [definition.useTransformFeedback] - Specifies that this shader outputs
* post-VS data to a buffer.
* @param {string} [definition.shaderLanguage] - Specifies the shader language of vertex and
Expand Down Expand Up @@ -89,15 +92,19 @@ class Shader {
this.device = graphicsDevice;
this.definition = definition;
this.name = definition.name || 'Untitled';
this.init();

Debug.assert(definition.vshader, 'No vertex shader has been specified when creating a shader.');
Debug.assert(definition.fshader, 'No fragment shader has been specified when creating a shader.');

// pre-process shader sources
definition.vshader = Preprocessor.run(definition.vshader);
definition.fshader = Preprocessor.run(definition.fshader, graphicsDevice.isWebGL2);
if (definition.cshader) {
Debug.assert(graphicsDevice.supportsCompute, 'Compute shaders are not supported on this device.');
Debug.assert(!definition.vshader && !definition.fshader, 'Vertex and fragment shaders are not supported when creating a compute shader.');
} else {
Debug.assert(definition.vshader, 'No vertex shader has been specified when creating a shader.');
Debug.assert(definition.fshader, 'No fragment shader has been specified when creating a shader.');

this.init();
// pre-process shader sources
definition.vshader = Preprocessor.run(definition.vshader);
definition.fshader = Preprocessor.run(definition.fshader, graphicsDevice.isWebGL2);
}

this.impl = graphicsDevice.createShaderImpl(this);

Expand Down
4 changes: 2 additions & 2 deletions src/platform/graphics/webgl/webgl-graphics-device.js
Expand Up @@ -1408,7 +1408,7 @@ class WebglGraphicsDevice extends GraphicsDevice {
* @param {import('../render-pass.js').RenderPass} renderPass - The render pass to start.
* @ignore
*/
startPass(renderPass) {
startRenderPass(renderPass) {

DebugGraphics.pushGpuMarker(this, `START-PASS`);

Expand Down Expand Up @@ -1470,7 +1470,7 @@ class WebglGraphicsDevice extends GraphicsDevice {
* @param {import('../render-pass.js').RenderPass} renderPass - The render pass to end.
* @ignore
*/
endPass(renderPass) {
endRenderPass(renderPass) {

DebugGraphics.pushGpuMarker(this, `END-PASS`);

Expand Down
61 changes: 61 additions & 0 deletions src/platform/graphics/webgpu/webgpu-compute-pipeline.js
@@ -0,0 +1,61 @@
import { Debug, DebugHelper } from "../../../core/debug.js";
import { TRACEID_COMPUTEPIPELINE_ALLOC } from "../../../core/constants.js";
import { WebgpuDebug } from "./webgpu-debug.js";

let _pipelineId = 0;

/**
* @ignore
*/
class WebgpuComputePipeline {
constructor(device) {
/** @type {import('./webgpu-graphics-device.js').WebgpuGraphicsDevice} */
this.device = device;
}

/** @private */
get(shader) {

const pipeline = this.create(shader);
return pipeline;
}

create(shader) {

const wgpu = this.device.wgpu;

/** @type {import('./webgpu-shader.js').WebgpuShader} */
const webgpuShader = shader.impl;

/** @type {GPUComputePipelineDescriptor} */
const descr = {
compute: {
module: webgpuShader.getComputeShaderModule(),
entryPoint: webgpuShader.computeEntryPoint
},

// uniform / texture binding layout
layout: 'auto'
};

WebgpuDebug.validate(this.device);

_pipelineId++;
DebugHelper.setLabel(descr, `ComputePipelineDescr-${_pipelineId}`);

const pipeline = wgpu.createComputePipeline(descr);

DebugHelper.setLabel(pipeline, `ComputePipeline-${_pipelineId}`);
Debug.trace(TRACEID_COMPUTEPIPELINE_ALLOC, `Alloc: Id ${_pipelineId}`, descr);

WebgpuDebug.end(this.device, {
computePipeline: this,
descr,
shader
});

return pipeline;
}
}

export { WebgpuComputePipeline };
30 changes: 30 additions & 0 deletions src/platform/graphics/webgpu/webgpu-compute.js
@@ -0,0 +1,30 @@
/**
* A WebGPU implementation of the Compute.
*
* @ignore
*/
class WebgpuCompute {
constructor(compute) {
this.compute = compute;

const { device, shader } = compute;
this.pipeline = device.computePipeline.get(shader);
}

dispatch(x, y, z) {

// TODO: currently each dispatch is a separate compute pass, which is not optimal, and we should
// batch multiple dispatches into a single compute pass
const device = this.compute.device;
device.startComputePass();

// dispatch
const passEncoder = device.passEncoder;
passEncoder.setPipeline(this.pipeline);
passEncoder.dispatchWorkgroups(x, y, z);

device.endComputePass();
}
}

export { WebgpuCompute };