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