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.

261 lines (258 loc) 9.96 kB
import { isRenderer } from '../renderers/utils.mjs'; import { TextureBinding } from '../bindings/TextureBinding.mjs'; import { generateUUID } from '../../utils/utils.mjs'; import { getNumMipLevels, getDefaultTextureUsage } from './utils.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"), 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 _autoResize; const defaultTextureParams = { label: "Texture", name: "renderTexture", // default to 'renderTexture' for render target usage type: "texture", access: "write", fromTexture: null, viewDimension: "2d", sampleCount: 1, qualityRatio: 1, // copy external texture options generateMips: false, flipY: false, premultipliedAlpha: false, aspect: "all", colorSpace: "srgb", autoDestroy: true }; class Texture { /** * Texture constructor * @param renderer - {@link Renderer | renderer} object or {@link GPUCurtains} class object used to create this {@link Texture}. * @param parameters - {@link TextureParams | parameters} used to create this {@link Texture}. */ constructor(renderer, parameters = defaultTextureParams) { /** Whether this texture should be automatically resized when the {@link Renderer renderer} size changes. Default to true. */ __privateAdd(this, _autoResize, true); renderer = isRenderer(renderer, parameters.label ? parameters.label + " Texture" : "Texture"); this.type = "Texture"; this.renderer = renderer; this.uuid = generateUUID(); this.options = { ...defaultTextureParams, ...parameters }; if (this.options.format === "rgba32float" && this.renderer.device && !this.renderer.device.features.has("float32-filterable")) { this.options.format = "rgba16float"; } if (parameters.fromTexture) { this.options.format = parameters.fromTexture.texture.format; this.options.sampleCount = parameters.fromTexture.texture.sampleCount; this.options.viewDimension = parameters.fromTexture.options.viewDimension; } if (!this.options.format) { this.options.format = this.renderer.options.context.format; } const { width, height } = this.renderer.canvas; this.size = this.options.fixedSize ? { width: this.options.fixedSize.width * this.options.qualityRatio, height: this.options.fixedSize.height * this.options.qualityRatio, depth: this.options.fixedSize.depth ?? this.options.viewDimension.indexOf("cube") !== -1 ? 6 : 1 } : { width: Math.floor(width * this.options.qualityRatio), height: Math.floor(height * this.options.qualityRatio), depth: this.options.viewDimension.indexOf("cube") !== -1 ? 6 : 1 }; if (this.options.fixedSize) { __privateSet(this, _autoResize, false); } this.setBindings(); this.renderer.addTexture(this); this.createTexture(); } /** * Reset this {@link Texture} {@link Texture.renderer | renderer}, and resize it if needed. * @param renderer - New {@link Renderer} or {@link GPUCurtains} instance to use. */ setRenderer(renderer) { if (this.renderer) { this.renderer.removeTexture(this); } renderer = isRenderer(renderer, this.options.label + " Texture"); this.renderer = renderer; this.renderer.addTexture(this); const { width, height } = this.renderer.canvas; if (__privateGet(this, _autoResize) && (this.size.width !== width * this.options.qualityRatio || this.size.height !== height * this.options.qualityRatio)) { this.resize(); } } /** * Set our {@link Texture#bindings | bindings}. */ setBindings() { this.bindings = [ new TextureBinding({ label: this.options.label + ": " + this.options.name + " texture", name: this.options.name, bindingType: this.options.type, visibility: this.options.visibility, texture: this.texture, format: this.options.format, viewDimension: this.options.viewDimension, multisampled: this.options.sampleCount > 1 }) ]; } /** * Get our {@link TextureBinding | texture binding}. * @readonly */ get textureBinding() { return this.bindings[0]; } /** * Copy another {@link Texture} into this {@link Texture}. * @param texture - {@link Texture} to copy. */ copy(texture) { this.options.fromTexture = texture; this.createTexture(); } /** * Copy a {@link GPUTexture} directly into this {@link Texture}. Mainly used for depth textures. * @param texture - {@link GPUTexture} to copy. */ copyGPUTexture(texture) { this.size = { width: texture.width, height: texture.height, depth: texture.depthOrArrayLayers }; this.options.format = texture.format; this.options.sampleCount = texture.sampleCount; this.texture = texture; this.textureBinding.setFormat(this.options.format); this.textureBinding.setMultisampled(this.options.sampleCount > 1); this.textureBinding.resource = this.texture; } /** * Create the {@link GPUTexture | texture} (or copy it from source) and update the {@link TextureBinding#resource | binding resource}. */ createTexture() { if (!this.renderer.device || !this.size.width || !this.size.height) return; if (this.options.fromTexture) { this.copyGPUTexture(this.options.fromTexture.texture); return; } this.texture?.destroy(); this.texture = this.renderer.createTexture({ label: this.options.label, format: this.options.format, size: [this.size.width, this.size.height, this.size.depth ?? 1], dimensions: this.options.viewDimension, sampleCount: this.options.sampleCount, mipLevelCount: this.options.generateMips ? getNumMipLevels(this.size.width, this.size.height, this.size.depth ?? 1) : 1, usage: getDefaultTextureUsage(this.options.usage, this.options.type) }); this.textureBinding.resource = this.texture; } /** * Upload a source to the GPU and use it for our {@link texture}. * @param parameters - parameters used to upload the source. * @param parameters.source - source to use for our {@link texture}. * @param parameters.width - source width. * @param parameters.height - source height. * @param parameters.depth - source depth. * @param parameters.origin - {@link GPUQueue.copyExternalImageToTexture().destination.origin | GPUOrigin3D} of the source copy. */ uploadSource({ source, width = this.size.width, height = this.size.height, depth = this.size.depth, origin = [0, 0, 0], colorSpace = "srgb" }) { this.renderer.deviceManager.copyExternalImageToTexture( { source, flipY: this.options.flipY }, { texture: this.texture, premultipliedAlpha: this.options.premultipliedAlpha, origin, colorSpace }, [width, height, depth] ); if (this.texture.mipLevelCount > 1) { this.renderer.generateMips(this); } } /** * Use data as the {@link texture} source and upload it to the GPU. * @param parameters - parameters used to upload the source. * @param parameters.width - data source width. * @param parameters.height - data source height. * @param parameters.depth - data source depth. * @param parameters.origin - {@link GPUQueue.copyExternalImageToTexture().destination.origin | GPUOrigin3D} of the data source copy. * @param parameters.data - {@link Float32Array} data to use as source. */ uploadData({ width = this.size.width, height = this.size.height, depth = this.size.depth, origin = [0, 0, 0], data = new Float32Array(width * height * 4) }) { this.renderer.device.queue.writeTexture( { texture: this.texture, origin }, data, { bytesPerRow: width * data.BYTES_PER_ELEMENT * 4, rowsPerImage: height }, [width, height, depth] ); if (this.texture.mipLevelCount > 1) { this.renderer.generateMips(this); } } /** * Update our {@link Texture} quality ratio and resize it. * @param qualityRatio - New quality ratio to use. */ setQualityRatio(qualityRatio = 1) { this.options.qualityRatio = qualityRatio; this.resize(); } /** * Resize our {@link Texture}, which means recreate it/copy it again and tell the {@link core/bindGroups/TextureBindGroup.TextureBindGroup | texture bind group} to update. * @param size - the optional new {@link TextureSize | size} to set. */ resize(size = null) { if (!__privateGet(this, _autoResize)) return; if (!size) { const { width, height } = this.renderer.canvas; size = { width: Math.floor(width * this.options.qualityRatio), height: Math.floor(height * this.options.qualityRatio), depth: this.size.depth }; } if (size.width === this.size.width && size.height === this.size.height && size.depth === this.size.depth) { return; } this.setSize(size); } /** * Set our {@link Texture} {@link Texture.size | size}, recreate it/copy it again and tell the {@link core/bindGroups/TextureBindGroup.TextureBindGroup | texture bind group} to update. * @param size - the new {@link TextureSize | size} to set. */ setSize(size) { this.size = size; this.createTexture(); } /** * Destroy our {@link Texture}. */ destroy() { this.renderer.removeTexture(this); if (!this.options.fromTexture) { this.texture?.destroy(); } this.texture = null; } } _autoResize = new WeakMap(); export { Texture };