UNPKG

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.

142 lines (141 loc) 6.73 kB
import { isRenderer } from "../renderers/utils.mjs"; import { Material } from "./Material.mjs"; //#region src/core/materials/ComputeMaterial.ts /** * Create a {@link Material} specifically built to run computations on the GPU. Internally used by {@link core/computePasses/ComputePass.ComputePass | ComputePass}. * * ## Compute pipeline * * A {@link ComputeMaterial} automatically creates a {@link ComputePipelineEntry}. Once all the {@link core/bindGroups/BindGroup.BindGroup | BindGroup} have been created, they are sent with the compute shader code to the {@link ComputePipelineEntry}, which is in turns responsible for creating the {@link GPUComputePipeline}. * * After the {@link GPUComputePipeline} has been successfully compiled, the {@link ComputeMaterial} is considered to be ready and it can start running the compute shader computations. * */ var ComputeMaterial = class extends Material { /** * ComputeMaterial constructor * @param renderer - {@link Renderer} class object or {@link GPUCurtains} class object used to create this {@link ComputeMaterial}. * @param parameters - {@link ComputeMaterialParams | parameters} used to create our {@link ComputeMaterial}. */ constructor(renderer, parameters) { const type = "ComputeMaterial"; renderer = isRenderer(renderer, type); super(renderer, parameters); this.type = type; this.renderer = renderer; let { shaders, dispatchSize } = parameters; if (!shaders || !shaders.compute) shaders = { compute: { code: "", entryPoint: "main" } }; if (!shaders.compute.code) shaders.compute.code = "@compute @workgroup_size(1) fn main(){}"; if (!shaders.compute.entryPoint) shaders.compute.entryPoint = "main"; this.options = { ...this.options, shaders, ...parameters.dispatchSize !== void 0 && { dispatchSize: parameters.dispatchSize } }; if (!dispatchSize) dispatchSize = 1; if (Array.isArray(dispatchSize)) { dispatchSize[0] = Math.ceil(dispatchSize[0] ?? 1); dispatchSize[1] = Math.ceil(dispatchSize[1] ?? 1); dispatchSize[2] = Math.ceil(dispatchSize[2] ?? 1); } else if (!isNaN(dispatchSize)) dispatchSize = [ Math.ceil(dispatchSize), 1, 1 ]; this.dispatchSize = dispatchSize; } /** * Set (or reset) the current {@link pipelineEntry}. Use the {@link Renderer#pipelineManager | renderer pipelineManager} to check whether we can get an already created {@link ComputePipelineEntry} from cache or if we should create a new one. */ setPipelineEntry() { this.pipelineEntry = this.renderer.pipelineManager.createComputePipeline(this); } /** * Compile the {@link ComputePipelineEntry}. */ async compilePipelineEntry() { await this.pipelineEntry.compilePipelineEntry(); } /** * Check if all bind groups are ready, create them if needed, set {@link ComputePipelineEntry} bind group buffers and compile the pipeline. */ async compileMaterial() { if (this.ready) return; await super.compileMaterial(); if (!this.pipelineEntry) this.setPipelineEntry(); if (this.pipelineEntry && this.pipelineEntry.canCompile) await this.compilePipelineEntry(); } /** * Get the complete code of a given shader including all the WGSL fragment code snippets added by the pipeline. Can wait for the {@link pipelineEntry} to be compiled if that's not already the case. * @param [shaderType="compute"] - Shader to get the code from. * @returns - The corresponding shader code. */ async getShaderCode(shaderType = "compute") { return await super.getShaderCode(shaderType); } /** * Get the added code of a given shader, i.e. all the WGSL fragment code snippets added by the pipeline. Can wait for the {@link pipelineEntry} to be compiled if that's not already the case. * @param [shaderType="compute"] - Shader to get the code from * @returns - The corresponding shader code */ async getAddedShaderCode(shaderType = "compute") { return await super.getAddedShaderCode(shaderType); } /** * If a custom render function has been defined instead of the default one, register the callback * @param callback - callback to run instead of the default render behaviour, which is to set the {@link bindGroups | bind groups} and dispatch the work groups based on the {@link dispatchSize | default dispatch size}. This is where you will have to set all the {@link core/bindGroups/BindGroup.BindGroup | bind groups} and dispatch the workgroups by yourself. */ useCustomRender(callback) { if (callback) this._useCustomRenderCallback = callback; } /** * Render the material if it is ready: * Set the current pipeline, set the bind groups and dispatch the work groups. * @param pass - Current compute pass encoder. */ render(pass) { if (!this.ready) return; this.setPipeline(pass); if (this._useCustomRenderCallback !== void 0) this._useCustomRenderCallback(pass); else { for (const bindGroup of this.bindGroups) pass.setBindGroup(bindGroup.index, bindGroup.bindGroup); pass.dispatchWorkgroups(this.dispatchSize[0], this.dispatchSize[1], this.dispatchSize[2]); } } /** * Copy all writable binding buffers that need it. * @param commandEncoder - Current command encoder. */ copyBufferToResult(commandEncoder) { for (const bindGroup of this.bindGroups) bindGroup.bufferBindings.forEach((binding) => { if (binding.shouldCopyResult) this.renderer.copyBufferToBuffer({ srcBuffer: binding.buffer, dstBuffer: binding.resultBuffer, commandEncoder }); }); } /** * Get the {@link core/bindings/WritableBufferBinding.WritableBufferBinding#resultBuffer | result GPU buffer} content by {@link core/bindings/WritableBufferBinding.WritableBufferBinding | binding} and {@link core/bindings/bufferElements/BufferElement.BufferElement | buffer element} names. * @param parameters - Parameters used to get the result. * @param parameters.bindingName - {@link core/bindings/WritableBufferBinding.WritableBufferBinding#name | binding name} from which to get the result. * @param parameters.bufferElementName - Pptional {@link core/bindings/bufferElements/BufferElement.BufferElement | buffer element} (i.e. struct member) name if the result needs to be restrained to only one element. * @returns - the mapped content of the {@link GPUBuffer} as a {@link Float32Array}. */ async getComputeResult({ bindingName = "", bufferElementName = "" }) { const binding = this.getBufferBindingByName(bindingName); if (binding && "resultBuffer" in binding) { const result = await this.getBufferResult(binding.resultBuffer); if (bufferElementName && result.length) return binding.extractBufferElementDataFromBufferResult({ result, bufferElementName }); else return result; } else return new Float32Array(0); } }; //#endregion export { ComputeMaterial };