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.

168 lines (167 loc) 6.12 kB
import { generateUUID } from "../../utils/utils.mjs"; import { isRenderer } from "../../core/renderers/utils.mjs"; import { Buffer } from "../../core/buffers/Buffer.mjs"; //#region src/extras/buffers/IndirectBuffer.ts const indirectBufferEntrySize = 5; /** * Utility to handle indirect drawing. * * Create a {@link buffer}, fill it with all the added {@link geometries} attributes and tell all the {@link geometries} to start using this {@link buffer} for indirect drawing. * * @example * ```javascript * const geometry = new Geometry() * * // assuming 'renderer' is a valid renderer or curtains instance * const indirectBuffer = new IndirectBuffer(renderer, { * label: 'Custom indirect buffer', * geometries: [geometry] * }) * * // if every geometries have been added, create the buffer. * indirectBuffer.create() * * // from now on, any Mesh using 'geometry' as geometry will be rendered using indirect drawing. * ``` */ var IndirectBuffer = class { /** * IndirectBuffer constructor. * @param renderer - {@link Renderer} or {@link GPUCurtains} class object used to create this {@link IndirectBuffer}. * @param parameters - {@link IndirectBufferParams | parameters} used to create this {@link IndirectBuffer}. */ constructor(renderer, { label = "Indirect buffer", geometries = [], minEntrySize = indirectBufferEntrySize } = {}) { this.type = "IndirectBuffer"; this.setRenderer(renderer); this.uuid = generateUUID(); this.options = { label, geometries, minEntrySize }; this.geometries = /* @__PURE__ */ new Map(); this.buffer = null; this.addGeometries(geometries); this.renderer.indirectBuffers.set(this.uuid, this); } /** * Set or reset this {@link IndirectBuffer} {@link IndirectBuffer.renderer | renderer}. * @param renderer - New {@link Renderer} or {@link GPUCurtains} instance to use. */ setRenderer(renderer) { renderer = isRenderer(renderer, this.type); this.renderer = renderer; } /** * Get the number of unique {@link Geometry} and {@link IndexedGeometry} added to this {@link IndirectBuffer}. * @returns - Number of unique {@link Geometry} and {@link IndexedGeometry} added to this {@link IndirectBuffer}. * @readonly */ get size() { return this.geometries.size; } /** * Add multiple {@link Geometry} or {@link IndexedGeometry} to this {@link IndirectBuffer}. * @param geometries - Array of {@link Geometry} or {@link IndexedGeometry} to add to this {@link IndirectBuffer}. */ addGeometries(geometries = []) { geometries.forEach((geometry) => this.addGeometry(geometry)); } /** * Add a {@link Geometry} or {@link IndexedGeometry} to this {@link IndirectBuffer}. * @param geometry - A {@link Geometry} or {@link IndexedGeometry} to add to this {@link IndirectBuffer}. */ addGeometry(geometry) { this.geometries.set(geometry.uuid, geometry); } /** * Get the byte offset in the {@link buffer} at a given index. * @param index - Index to get the byte offset from. * @returns - Byte offset in the {@link buffer} at a given index. */ getByteOffsetAtIndex(index = 0) { return index * this.options.minEntrySize * Uint32Array.BYTES_PER_ELEMENT; } /** * Create the {@link buffer} (or destroy it if it already exists) with the right size, create its {@link GPUBuffer} in a mapped state, add all {@link geometries} attributes to the mapped buffer and tell the {@link geometries} to use this {@link IndirectBuffer}. * @private */ #createIndirectBuffer() { const size = this.getByteOffsetAtIndex(this.geometries.size); if (this.buffer) { this.buffer.destroy(); this.buffer.options.size = size; } else this.buffer = new Buffer({ label: this.options.label, size, usage: [ "copyDst", "indirect", "storage" ], mappedAtCreation: true }); this.buffer.consumers.add(this.uuid); this.buffer.createBuffer(this.renderer); const indirectMappedBuffer = new Uint32Array(this.buffer.GPUBuffer.getMappedRange()); let offset = 0; this.geometries.forEach((geometry) => { this.#addGeometryToIndirectMappedBuffer(geometry, indirectMappedBuffer, offset * this.options.minEntrySize); geometry.useIndirectBuffer({ buffer: this.buffer, offset: this.getByteOffsetAtIndex(offset) }); offset++; }); this.buffer.GPUBuffer.unmap(); } /** * Create the {@link buffer} as soon as the {@link core/renderers/GPURenderer.GPURenderer#device | device} is ready. */ create() { if (this.renderer.ready) this.#createIndirectBuffer(); else { const taskId = this.renderer.onBeforeCommandEncoderCreation.add(() => { if (this.renderer.device) { this.renderer.onBeforeCommandEncoderCreation.remove(taskId); this.#createIndirectBuffer(); } }, { once: false }); } } /** * Add a {@link Geometry} or {@link IndexedGeometry} attributes to the {@link buffer} mapped array buffer. * @param geometry - {@link Geometry} or {@link IndexedGeometry} to add the attributes from * @param mappedBuffer - The {@link buffer} mapped array buffer * @param index - Index in the {@link buffer} mapped array buffer at which to add the attributes. * @private */ #addGeometryToIndirectMappedBuffer(geometry, mappedBuffer, index = 0) { if ("indexBuffer" in geometry && geometry.indexBuffer) { mappedBuffer[index] = geometry.indexBuffer.bufferLength; mappedBuffer[index + 1] = geometry.instancesCount; mappedBuffer[index + 2] = 0; mappedBuffer[index + 3] = 0; mappedBuffer[index + 4] = 0; } else { mappedBuffer[index] = geometry.verticesCount; mappedBuffer[index + 1] = geometry.instancesCount; mappedBuffer[index + 2] = 0; mappedBuffer[index + 3] = 0; mappedBuffer[index + 4] = 0; } } /** * Destroy this {@link IndirectBuffer}. Reset all {@link geometries} {@link Geometry#indirectDraw | indirectDraw} properties and destroy the {@link Buffer}. */ destroy() { this.renderer.removeBuffer(this.buffer); this.renderer.indirectBuffers.delete(this.uuid); this.geometries.forEach((geometry) => geometry.indirectDraw = null); this.buffer?.destroy(); this.buffer = null; this.geometries = null; } }; //#endregion export { IndirectBuffer };