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.

211 lines (210 loc) 7.48 kB
import { isRenderer } from "../renderers/utils.mjs"; import { BufferBinding } from "../bindings/BufferBinding.mjs"; import { BindGroup } from "./BindGroup.mjs"; import { MediaTexture } from "../textures/MediaTexture.mjs"; import { DOMTexture } from "../../curtains/textures/DOMTexture.mjs"; //#region src/core/bindGroups/TextureBindGroup.ts /** * Used to regroup all {@link types/BindGroups.BindGroupBindingElement | bindings} related to textures (texture, texture matrices buffers and samplers) into one single specific {@link BindGroup}. * * Also responsible for uploading video textures if needed. * * @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() * * // create a texture * const texture = new Texture(gpuCurtains, { * label: 'Input texture', * name: 'inputTexture', * }) * * // create a texture bind group using that texture * const textureBindGroup = new TextureBindGroup(gpuCurtains, { * label: 'My texture bind group', * textures: [texture], * uniforms: { * params: { * struct: { * opacity: { * type: 'f32', * value: 1, * }, * mousePosition: { * type: 'vec2f', * value: new Vec2(), * }, * }, * }, * }, * }) * * // create the GPU buffer, bindGroupLayout and bindGroup * textureBindGroup.createBindGroup() * ``` */ var TextureBindGroup = class extends BindGroup { /** * Array containing all the {@link MediaTexture} that handle a transformation {@link MediaTexture#modelMatrix | modelMatrix}. * @private */ #transformedTextures; /** * TextureBindGroup constructor * @param renderer - a {@link Renderer} class object or a {@link GPUCurtains} class object. * @param parameters - {@link TextureBindGroupParams | parameters} used to create our {@link TextureBindGroup}. */ constructor(renderer, { label, index = 0, bindings = [], uniforms, storages, textures = [], samplers = [] } = {}) { const type = "TextureBindGroup"; renderer = isRenderer(renderer, type); super(renderer, { label, index, bindings, uniforms, storages }); this.options = { ...this.options, textures: [], samplers: [] }; if (textures.length) for (const texture of textures) this.addTexture(texture); if (samplers.length) for (const sampler of samplers) this.addSampler(sampler); this.type = type; this.texturesMatricesBinding = null; } /** * Set or reset this {@link TextureBindGroup} {@link TextureBindGroup.renderer | renderer}, and update the {@link samplers} and {@link textures} renderer as well. * @param renderer - New {@link Renderer} or {@link GPUCurtains} instance to use. */ setRenderer(renderer) { const shadowTextures = /* @__PURE__ */ new Set(); if (this.renderer && "shadowCastingLights" in this.renderer) this.renderer.shadowCastingLights.forEach((light) => { if (light.shadow.isActive && light.shadow.depthTexture) shadowTextures.add(light.shadow.depthTexture.uuid); }); super.setRenderer(renderer); if (this.options && this.samplers) this.samplers.forEach((sampler) => { sampler.setRenderer(this.renderer); }); if (this.options && this.textures) this.textures.forEach((texture) => { if (!shadowTextures.has(texture.uuid)) texture.setRenderer(this.renderer); }); } /** * Adds a texture to the {@link textures} array and {@link bindings}. * @param texture - texture to add. */ addTexture(texture) { this.textures.push(texture); this.addBindings([...texture.bindings]); } /** * Get the current {@link textures} array. * @readonly */ get textures() { return this.options.textures; } /** * Adds a sampler to the {@link samplers} array and {@link bindings}. * @param sampler */ addSampler(sampler) { this.samplers.push(sampler); this.addBindings([sampler.binding]); } /** * Get the current {@link samplers} array. * @readonly */ get samplers() { return this.options.samplers; } /** * Get whether the GPU bind group is ready to be created. * It can be created if it has {@link BindGroup#bindings} and has not been created yet and all {@link GPUTexture} and {@link GPUSampler} are created. * @readonly */ get shouldCreateBindGroup() { return !this.bindGroup && !!this.bindings.length && !this.textures.find((texture) => !(texture.texture || texture.externalTexture)) && !this.samplers.find((sampler) => !sampler.sampler); } /** * Set the {@link texturesMatricesBinding} if needed. */ setTexturesMatricesBinding() { this.#transformedTextures = this.textures.filter((texture) => (texture instanceof MediaTexture || texture instanceof DOMTexture) && !!texture.transformBinding); const texturesBindings = this.#transformedTextures.map((texture) => { return texture.transformBinding; }); if (texturesBindings.length) { const label = "Textures matrices"; const name = "texturesMatrices"; this.texturesMatricesBinding = new BufferBinding({ label, name, childrenBindings: texturesBindings.map((textureBinding) => { return { binding: textureBinding, count: 1, forceArray: false }; }) }); this.texturesMatricesBinding.childrenBindings.forEach((childrenBinding, i) => { childrenBinding.inputs.matrix.value = texturesBindings[i].inputs.matrix.value; }); this.texturesMatricesBinding.buffer.consumers.add(this.uuid); if (!this.bindings.find((binding) => binding.name === name)) this.addBinding(this.texturesMatricesBinding); } } /** * Create the {@link texturesMatricesBinding} and {@link bindGroup}. */ createBindGroup() { this.setTexturesMatricesBinding(); super.createBindGroup(); } /** * Update the {@link TextureBindGroup#textures | bind group textures}: * - Check if they need to copy their source texture. * - Upload video texture if needed. */ updateTextures() { for (const texture of this.textures) if (texture instanceof MediaTexture) { if (texture.options.fromTexture && texture.options.fromTexture instanceof MediaTexture && texture.options.fromTexture.sourcesUploaded && !texture.sourcesUploaded) texture.copy(texture.options.fromTexture); const firstSource = texture.sources.length && texture.sources[0]; if (firstSource && firstSource.shouldUpdate && texture.options.sourcesTypes[0] && texture.options.sourcesTypes[0] === "externalVideo") texture.uploadVideoTexture(); } if (this.texturesMatricesBinding) this.#transformedTextures.forEach((texture, i) => { this.texturesMatricesBinding.childrenBindings[i].inputs.matrix.shouldUpdate = !!texture.transformBinding.inputs.matrix.shouldUpdate; if (texture.transformBinding.inputs.matrix.shouldUpdate) this.renderer.onAfterCommandEncoderSubmission.add(() => { texture.transformBinding.inputs.matrix.shouldUpdate = false; }, { once: true }); }); } /** * Update the {@link TextureBindGroup}, which means update its {@link TextureBindGroup#textures | textures}, then update its {@link TextureBindGroup#bufferBindings | buffer bindings} and finally {@link TextureBindGroup#resetBindGroup | reset it} if needed. */ update() { this.updateTextures(); super.update(); } /** * Destroy our {@link TextureBindGroup}. */ destroy() { super.destroy(); this.options.textures = []; this.options.samplers = []; } }; //#endregion export { TextureBindGroup };