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.

400 lines (397 loc) 14.2 kB
import { isRenderer } from '../renderers/utils.mjs'; import { generateUUID } from '../../utils/utils.mjs'; import { ComputeMaterial } from '../materials/ComputeMaterial.mjs'; import { Texture } from '../textures/Texture.mjs'; import { MediaTexture } from '../textures/MediaTexture.mjs'; var __typeError = (msg) => { throw TypeError(msg); }; var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg); var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value); var _autoRender, _active; let computePassIndex = 0; class ComputePass { /** * ComputePass constructor * @param renderer - {@link Renderer} class object or {@link GPUCurtains} class object used to create this {@link ComputePass}. * @param parameters - {@link ComputePassParams | parameters} used to create our {@link ComputePass}. */ constructor(renderer, parameters = {}) { /** * Whether this {@link ComputePass} should be added to our {@link core/scenes/Scene.Scene | Scene} to let it handle the rendering process automatically. * @private */ __privateAdd(this, _autoRender, true); /** Flag indicating whether this {@link ComputePass} should run or not, much like the {@link core/meshes/Mesh.Mesh#visible | Mesh visible} flag. */ __privateAdd(this, _active, true); // callbacks / events /** function assigned to the {@link onReady} callback. */ this._onReadyCallback = () => { }; /** function assigned to the {@link onBeforeRender} callback. */ this._onBeforeRenderCallback = () => { }; /** function assigned to the {@link onRender} callback. */ this._onRenderCallback = () => { }; /** function assigned to the {@link onAfterRender} callback. */ this._onAfterRenderCallback = () => { }; /** function assigned to the {@link onAfterResize} callback. */ this._onAfterResizeCallback = () => { }; const type = "ComputePass"; renderer = isRenderer(renderer, parameters.label ? `${parameters.label} ${type}` : type); parameters.label = parameters.label ?? "ComputePass " + renderer.computePasses?.length; this.renderer = renderer; this.type = type; this.uuid = generateUUID(); Object.defineProperty(this, "index", { value: computePassIndex++ }); const { label, shaders, renderOrder, uniforms, storages, bindings, bindGroups, samplers, textures, autoRender, active, useAsyncPipeline, texturesOptions, dispatchSize } = parameters; this.options = { label, shaders, ...autoRender !== void 0 && { autoRender }, ...active !== void 0 && { active }, ...renderOrder !== void 0 && { renderOrder }, ...dispatchSize !== void 0 && { dispatchSize }, useAsyncPipeline: useAsyncPipeline === void 0 ? true : useAsyncPipeline, texturesOptions // TODO default }; this.renderOrder = renderOrder ?? 0; if (autoRender !== void 0) { __privateSet(this, _autoRender, autoRender); } if (active !== void 0) { __privateSet(this, _active, active); } this.userData = {}; this.ready = false; this.setMaterial({ label: this.options.label, shaders: this.options.shaders, uniforms, storages, bindings, bindGroups, samplers, textures, useAsyncPipeline, dispatchSize }); this.addToScene(true); } /** * Get or set whether the compute pass is ready to render (the material has been successfully compiled). * @readonly */ get ready() { return this._ready; } set ready(value) { if (value) { this._onReadyCallback && this._onReadyCallback(); } this._ready = value; } /** * Add our {@link ComputePass} to the scene and optionally to the renderer. * @param addToRenderer - Whether to add this {@link ComputePass} to the {@link Renderer#computePasses | Renderer computePasses array}. */ addToScene(addToRenderer = false) { if (addToRenderer) { this.renderer.computePasses.push(this); } if (__privateGet(this, _autoRender)) { this.renderer.scene.addComputePass(this); } } /** * Remove our {@link ComputePass} from the scene and optionally from the renderer as well. * @param removeFromRenderer - whether to remove this {@link ComputePass} from the {@link Renderer#computePasses | Renderer computePasses array}. */ removeFromScene(removeFromRenderer = false) { if (__privateGet(this, _autoRender)) { this.renderer.scene.removeComputePass(this); } if (removeFromRenderer) { this.renderer.computePasses = this.renderer.computePasses.filter((computePass) => computePass.uuid !== this.uuid); } } /** * Set a new {@link Renderer} for this {@link ComputePass}. * @param renderer - new {@link Renderer} to set. */ setRenderer(renderer) { renderer = isRenderer(renderer, this.options.label + " ComputePass"); this.material?.setRenderer(renderer); this.removeFromScene(true); this.renderer = renderer; this.addToScene(true); } /** * Create the compute pass material. * @param computeParameters - {@link ComputeMaterial} parameters. */ setMaterial(computeParameters) { this.useMaterial(new ComputeMaterial(this.renderer, computeParameters)); } /** * Set or update the {@link ComputePass} {@link ComputeMaterial}. * @param material - New {@link ComputeMaterial} to use. */ useMaterial(material) { this.material = material; } /** * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been lost to prepare everything for restoration. * Basically set all the {@link GPUBuffer} to null so they will be reset next time we try to render. */ loseContext() { this.material.loseContext(); } /** * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been restored. */ restoreContext() { this.material.restoreContext(); } /** * Get the active property value. */ get active() { return __privateGet(this, _active); } /** * Set the active property value. * @param value - New active value. */ set active(value) { __privateSet(this, _active, value); } /* TEXTURES */ /** * Get our {@link ComputeMaterial#textures | ComputeMaterial textures array} * @readonly */ get textures() { return this.material?.textures || []; } /** * Create a new {@link MediaTexture}. * @param options - {@link MediaTextureParams | MediaTexture parameters}. * @returns - Newly created {@link MediaTexture}. */ createMediaTexture(options) { if (!options.name) { options.name = "texture" + this.textures.length; } if (!options.label) { options.label = this.options.label + " " + options.name; } const texture = new MediaTexture(this.renderer, { ...options, ...this.options.texturesOptions }); this.addTexture(texture); return texture; } /** * Create a new {@link Texture}. * @param options - {@link TextureParams | Texture parameters}. * @returns - newly created {@link Texture}. */ createTexture(options) { if (!options.name) { options.name = "texture" + this.textures.length; } const texture = new Texture(this.renderer, options); this.addTexture(texture); return texture; } /** * Add a {@link Texture} or {@link MediaTexture}. * @param texture - {@link Texture} to add. */ addTexture(texture) { this.material.addTexture(texture); } /** * Get our {@link ComputeMaterial#uniforms | ComputeMaterial uniforms}. * @readonly */ get uniforms() { return this.material?.uniforms; } /** * Get our {@link ComputeMaterial#storages | ComputeMaterial storages}. * @readonly */ get storages() { return this.material?.storages; } /** * Called from the renderer, useful to trigger an after resize callback. */ resize() { this._onAfterResizeCallback && this._onAfterResizeCallback(); } /** EVENTS **/ /** * Callback to run when the {@link ComputePass} is ready. * @param callback - Callback to run when {@link ComputePass} is ready. * @returns - Our {@link ComputePass}. */ onReady(callback) { if (callback) { this._onReadyCallback = callback; } return this; } /** * Callback to run before the {@link ComputePass} is rendered. * @param callback - Callback to run just before {@link ComputePass} will be rendered. The callback won't be called if the {@link Renderer} is not ready or the {@link ComputePass} itself is neither {@link ready} nor {@link active}. * @returns - Our {@link ComputePass}. */ onBeforeRender(callback) { if (callback) { this._onBeforeRenderCallback = callback; } return this; } /** * Callback to run when the {@link ComputePass} is rendered. * @param callback - Callback to run when {@link ComputePass} is rendered. The callback won't be called if the {@link Renderer} is not ready or the {@link ComputePass} itself is neither {@link ready} nor {@link active}. * @returns - Our {@link ComputePass}. */ onRender(callback) { if (callback) { this._onRenderCallback = callback; } return this; } /** * Callback to run after the {@link ComputePass} has been rendered. * @param callback - Callback to run just after {@link ComputePass} has been rendered. The callback won't be called if the {@link Renderer} is not ready or the {@link ComputePass} itself is neither {@link ready} nor {@link active}. * @returns - Our {@link ComputePass}. */ onAfterRender(callback) { if (callback) { this._onAfterRenderCallback = callback; } return this; } /** * Callback used to run a custom render function instead of the default one. This won't be called if the {@link Renderer} is not ready or the {@link ComputePass} itself is neither {@link ready} nor {@link active}. * @param callback - Your custom render function where you will have to set all the {@link core/bindGroups/BindGroup.BindGroup | bind groups} and dispatch the workgroups by yourself. * @returns - Our {@link ComputePass}. */ useCustomRender(callback) { this.material.useCustomRender(callback); return this; } /** * Callback to run after the {@link core/renderers/GPURenderer.GPURenderer | renderer} has been resized. * @param callback - Callback to run just after {@link core/renderers/GPURenderer.GPURenderer | renderer} has been resized. * @returns - Our {@link ComputePass}. */ onAfterResize(callback) { if (callback) { this._onAfterResizeCallback = callback; } return this; } /** * Called before rendering the {@link ComputePass}. * Checks if the material is ready and eventually update its bindings. */ onBeforeRenderPass() { if (!this.renderer.ready) return; if (this.active) { this._onBeforeRenderCallback && this._onBeforeRenderCallback(); } this.material.onBeforeRender(); if (this.material && this.material.ready && !this.ready) { this.ready = true; } } /** * Render our {@link ComputeMaterial}. * @param pass - Current compute pass encoder. */ onRenderPass(pass) { if (!this.material.ready) return; this._onRenderCallback && this._onRenderCallback(); this.material.render(pass); } /** * Called after having rendered the {@link ComputePass}. */ onAfterRenderPass() { this._onAfterRenderCallback && this._onAfterRenderCallback(); } /** * Render our compute pass. * Basically just check if our {@link core/renderers/GPURenderer.GPURenderer | renderer} is ready, and then render our {@link ComputeMaterial}. * @param pass - Current compute pass encoder. */ render(pass) { this.onBeforeRenderPass(); if (!this.renderer.ready || !this.active) return; !this.renderer.production && pass.pushDebugGroup(this.options.label); this.onRenderPass(pass); !this.renderer.production && pass.popDebugGroup(); this.onAfterRenderPass(); } /** * Copy the result of our read/write GPUBuffer into our result binding array. * @param commandEncoder - Current GPU command encoder. */ copyBufferToResult(commandEncoder) { this.material?.copyBufferToResult(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 - Optional {@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 }) { return await this.material?.getComputeResult({ bindingName, bufferElementName }); } /** * Remove the {@link ComputePass} from the {@link core/scenes/Scene.Scene | Scene} and destroy it. */ remove() { this.removeFromScene(true); this.destroy(); } /** * Destroy the {@link ComputePass}. */ destroy() { this.material?.destroy(); } } _autoRender = new WeakMap(); _active = new WeakMap(); export { ComputePass };