gpu-curtains
Version:
gpu-curtains is a 3D WebGPU rendering engine. It can be used as a standalone 3D engine, but also includes extra classes focused on mapping 3d objects to DOM elements; It allows users to synchronize values such as position, sizing, or scale between them.
174 lines (172 loc) • 6.14 kB
JavaScript
import { isRenderer } from "../../core/renderers/utils.mjs";
import { Texture } from "../../core/textures/Texture.mjs";
import { ComputePass } from "../../core/computePasses/ComputePass.mjs";
import { ShaderPass } from "../../core/renderPasses/ShaderPass.mjs";
//#region src/extras/computePasses/ComputeShaderPass.ts
/**
* A special class used to leverage {@link ComputePass} shaders and {@link ShaderPass} for post processing effects.
*
* Allows to write post processing effects to a storage texture using a compute shader, which can be faster than regular {@link ShaderPass} in some cases.
*
* @example
* ```javascript
* // set our main GPUCurtains instance
* const gpuCurtains = new GPUCurtains({
* container: '#canvas' // selector of our WebGPU canvas container
* })
*
* // set the GPU device
* // note this is asynchronous
* await gpuCurtains.setDevice()
*
* const computeShaderPass = new ComputeShaderPass(gpuCurtains, {
* label: 'My compute shader pass',
* shaders: {
* compute: {
* code: computeShaderPassCode, // assume it is a valid WGSL compute shader
* },
* },
* textureDispatchSize: [16, 16], // divided by the render texture [width, height] internally
* })
* ```
*/
var ComputeShaderPass = class extends ComputePass {
/**
* ComputeShaderPass constructor
* @param renderer - {@link Renderer} class object or {@link GPUCurtains} class object used to create this {@link ComputeShaderPass}.
* @param parameters - {@link ComputeShaderPassParams | parameters} used to create our {@link ComputeShaderPass}.
*/
constructor(renderer, parameters = {}) {
renderer = isRenderer(renderer, parameters.label ? `${parameters.label} ComputeShaderPass` : "ComputeShaderPass");
const { shaders, useAsyncPipeline, texturesOptions, uniforms, storages, bindings, bindGroups, samplers, ...shaderPassParams } = parameters;
const { targets, renderOrder, autoRender, inputTarget, outputTarget, isPrePass, ...otherParams } = shaderPassParams;
let { label, textures, textureDispatchSize, visible, storageTextureParams } = otherParams;
label = label ?? "ComputeShaderPass " + renderer.computePasses?.length;
visible = visible === void 0 ? true : visible;
const defaultStorageTextureParams = {
name: "storageRenderTexture",
format: "rgba8unorm"
};
if (storageTextureParams) storageTextureParams = {
...defaultStorageTextureParams,
...storageTextureParams
};
else storageTextureParams = defaultStorageTextureParams;
if (!textureDispatchSize) textureDispatchSize = [16, 16];
if (Array.isArray(textureDispatchSize)) {
textureDispatchSize[0] = Math.ceil(textureDispatchSize[0] ?? 16);
textureDispatchSize[1] = Math.ceil(textureDispatchSize[1] ?? 16);
} else if (!isNaN(textureDispatchSize)) textureDispatchSize = [Math.ceil(textureDispatchSize), Math.ceil(textureDispatchSize)];
else textureDispatchSize = [16, 16];
const storageTexture = new Texture(renderer, {
label: `${label} storage render texture`,
...storageTextureParams,
type: "storage",
visibility: ["compute"],
usage: [
"copySrc",
"copyDst",
"textureBinding",
"storageBinding"
]
});
const renderTexture = new Texture(renderer, {
label: `${label} render texture`,
name: storageTextureParams.name,
visibility: ["fragment"],
fromTexture: storageTexture
});
const { shaderPassSampler } = otherParams;
const shaderPass = new ShaderPass(renderer, {
label: `${label} ShaderPass`,
autoRender,
shaders: { fragment: { code: `
struct VSOutput {
position: vec4f,
uv: vec2f,
};
fn main(fsInput: VSOutput) -> vec4f {
return textureSample(${storageTextureParams.name}, ${shaderPassSampler ? shaderPassSampler.name : "defaultSampler"}, fsInput.uv);
}` } },
renderOrder,
textures: [renderTexture],
...shaderPassSampler && { samplers: [shaderPassSampler] },
visible,
targets,
inputTarget,
outputTarget,
isPrePass
});
if (textures && textures.length) textures = [
storageTexture,
shaderPass.renderTexture,
...textures
];
else textures = [storageTexture, shaderPass.renderTexture];
const computeParams = {
label,
shaders,
useAsyncPipeline,
texturesOptions,
uniforms,
storages,
bindings,
bindGroups,
textures,
samplers,
autoRender: false,
active: visible,
dispatchSize: [Math.ceil(storageTexture.size.width / textureDispatchSize[0]), Math.ceil(storageTexture.size.height / textureDispatchSize[1])]
};
super(renderer, computeParams);
this.options = {
...this.options,
storageTextureParams,
textureDispatchSize,
...shaderPassSampler && { shaderPassSampler }
};
this.textureDispatchSize = textureDispatchSize;
this.shaderPass = shaderPass;
this.storageTexture = storageTexture;
this.renderTexture = renderTexture;
const scenePassEntry = this.renderer.scene.getObjectRenderPassEntry(this.shaderPass);
if (scenePassEntry) {
const _onBeforeRenderPass = scenePassEntry.onBeforeRenderPass;
scenePassEntry.onBeforeRenderPass = (commandEncoder, swapChainTexture) => {
_onBeforeRenderPass && _onBeforeRenderPass(commandEncoder, swapChainTexture);
this.renderer.renderSingleComputePass(commandEncoder, this, false);
};
}
}
/**
* Get whether the {@link ComputePass} and {@link ShaderPass} should run.
*/
get visible() {
return this.active;
}
/**
* Set whether the {@link ComputePass} and {@link ShaderPass} should run.
*/
set visible(value) {
this.active = value;
this.shaderPass.visible = value;
}
/**
* Update the dispatch size and resize.
*/
resize() {
this.material.dispatchSize = [Math.ceil(this.storageTexture.size.width / this.textureDispatchSize[0]), Math.ceil(this.storageTexture.size.height / this.textureDispatchSize[1])];
super.resize();
}
/**
* Destroy the {@link ComputeShaderPass}.
*/
destroy() {
this.shaderPass.remove();
this.storageTexture.destroy();
this.renderTexture.destroy();
super.destroy();
}
};
//#endregion
export { ComputeShaderPass };