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.

160 lines (157 loc) 6.46 kB
import { Material } from './Material.mjs'; import { isRenderer } from '../renderers/utils.mjs'; class ComputeMaterial 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); } /* RENDER */ /** * 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]); } } /* RESULT BUFFER */ /** * 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); } } } export { ComputeMaterial };