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
JavaScript
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 };