@luma.gl/core
Version:
The luma.gl core Device API
176 lines • 7.84 kB
JavaScript
// luma.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import { Buffer } from "../adapter/resources/buffer.js";
import { log } from "../utils/log.js";
import { makeShaderBlockLayout } from "../shadertypes/shader-types/shader-block-layout.js";
import { UniformBlock } from "./uniform-block.js";
import { ShaderBlockWriter } from "./shader-block-writer.js";
/**
* Smallest buffer size that can be used for uniform buffers.
*
* This is an allocation policy rather than part of {@link ShaderBlockLayout}.
* Layouts report the exact packed size, while the store applies any minimum
* buffer-size rule when allocating GPU buffers.
*
* TODO - does this depend on device?
*/
const minUniformBufferSize = 1024;
/**
* A uniform store holds a uniform values for one or more uniform blocks,
* - It can generate binary data for any uniform buffer
* - It can manage a uniform buffer for each block
* - It can update managed uniform buffers with a single call
* - It performs some book keeping on what has changed to minimize unnecessary writes to uniform buffers.
*/
export class UniformStore {
/** Device used to infer layout and allocate buffers. */
device;
/** Stores the uniform values for each uniform block */
uniformBlocks = new Map();
/** Flattened layout metadata for each block. */
shaderBlockLayouts = new Map();
/** Serializers for block-backed uniform data. */
shaderBlockWriters = new Map();
/** Actual buffer for the blocks */
uniformBuffers = new Map();
/**
* Creates a new {@link UniformStore} for the supplied device and block definitions.
*/
constructor(device, blocks) {
this.device = device;
for (const [bufferName, block] of Object.entries(blocks)) {
const uniformBufferName = bufferName;
// Create a layout object to help us generate correctly formatted binary uniform buffers
const shaderBlockLayout = makeShaderBlockLayout(block.uniformTypes ?? {}, {
layout: block.layout ?? getDefaultUniformBufferLayout(device)
});
const shaderBlockWriter = new ShaderBlockWriter(shaderBlockLayout);
this.shaderBlockLayouts.set(uniformBufferName, shaderBlockLayout);
this.shaderBlockWriters.set(uniformBufferName, shaderBlockWriter);
// Create a Uniform block to store the uniforms for each buffer.
const uniformBlock = new UniformBlock({ name: bufferName });
uniformBlock.setUniforms(shaderBlockWriter.getFlatUniformValues(block.defaultUniforms || {}));
this.uniformBlocks.set(uniformBufferName, uniformBlock);
}
}
/** Destroy any managed uniform buffers */
destroy() {
for (const uniformBuffer of this.uniformBuffers.values()) {
uniformBuffer.destroy();
}
}
/**
* Set uniforms
*
* Makes all group properties partial and eagerly propagates changes to any
* managed GPU buffers.
*/
setUniforms(uniforms) {
for (const [blockName, uniformValues] of Object.entries(uniforms)) {
const uniformBufferName = blockName;
const shaderBlockWriter = this.shaderBlockWriters.get(uniformBufferName);
const flattenedUniforms = shaderBlockWriter?.getFlatUniformValues((uniformValues || {}));
this.uniformBlocks.get(uniformBufferName)?.setUniforms(flattenedUniforms || {});
// We leverage logging in updateUniformBuffers(), even though slightly less efficient
// this.updateUniformBuffer(blockName);
}
this.updateUniformBuffers();
}
/**
* Returns the allocation size for the named uniform buffer.
*
* This may exceed the packed layout size because minimum buffer-size policy is
* applied at the store layer.
*/
getUniformBufferByteLength(uniformBufferName) {
const packedByteLength = this.shaderBlockLayouts.get(uniformBufferName)?.byteLength || 0;
return Math.max(packedByteLength, minUniformBufferSize);
}
/**
* Returns packed binary data that can be uploaded to the named uniform buffer.
*
* The returned view length matches the packed block size and is not padded to
* the store's minimum allocation size.
*/
getUniformBufferData(uniformBufferName) {
const uniformValues = this.uniformBlocks.get(uniformBufferName)?.getAllUniforms() || {};
const shaderBlockWriter = this.shaderBlockWriters.get(uniformBufferName);
return shaderBlockWriter?.getData(uniformValues) || new Uint8Array(0);
}
/**
* Creates an unmanaged uniform buffer initialized with the current or supplied values.
*/
createUniformBuffer(uniformBufferName, uniforms) {
if (uniforms) {
this.setUniforms(uniforms);
}
const byteLength = this.getUniformBufferByteLength(uniformBufferName);
const uniformBuffer = this.device.createBuffer({
usage: Buffer.UNIFORM | Buffer.COPY_DST,
byteLength
});
// Note that this clears the needs redraw flag
const uniformBufferData = this.getUniformBufferData(uniformBufferName);
uniformBuffer.write(uniformBufferData);
return uniformBuffer;
}
/** Returns the managed uniform buffer for the named block. */
getManagedUniformBuffer(uniformBufferName) {
if (!this.uniformBuffers.get(uniformBufferName)) {
const byteLength = this.getUniformBufferByteLength(uniformBufferName);
const uniformBuffer = this.device.createBuffer({
usage: Buffer.UNIFORM | Buffer.COPY_DST,
byteLength
});
this.uniformBuffers.set(uniformBufferName, uniformBuffer);
}
// this.updateUniformBuffers();
// @ts-ignore
return this.uniformBuffers.get(uniformBufferName);
}
/**
* Updates every managed uniform buffer whose source uniforms have changed.
*
* @returns The first redraw reason encountered, or `false` if nothing changed.
*/
updateUniformBuffers() {
let reason = false;
for (const uniformBufferName of this.uniformBlocks.keys()) {
const bufferReason = this.updateUniformBuffer(uniformBufferName);
reason ||= bufferReason;
}
if (reason) {
log.log(3, `UniformStore.updateUniformBuffers(): ${reason}`)();
}
return reason;
}
/**
* Updates one managed uniform buffer if its corresponding block is dirty.
*
* @returns The redraw reason for the update, or `false` if no write occurred.
*/
updateUniformBuffer(uniformBufferName) {
const uniformBlock = this.uniformBlocks.get(uniformBufferName);
let uniformBuffer = this.uniformBuffers.get(uniformBufferName);
let reason = false;
if (uniformBuffer && uniformBlock?.needsRedraw) {
reason ||= uniformBlock.needsRedraw;
// This clears the needs redraw flag
const uniformBufferData = this.getUniformBufferData(uniformBufferName);
uniformBuffer = this.uniformBuffers.get(uniformBufferName);
uniformBuffer?.write(uniformBufferData);
// logging - TODO - don't query the values unnecessarily
const uniformValues = this.uniformBlocks.get(uniformBufferName)?.getAllUniforms();
log.log(4, `Writing to uniform buffer ${String(uniformBufferName)}`, uniformBufferData, uniformValues)();
}
return reason;
}
}
/**
* Returns the default uniform-buffer layout for the supplied device.
*/
function getDefaultUniformBufferLayout(device) {
return device.type === 'webgpu' ? 'wgsl-uniform' : 'std140';
}
//# sourceMappingURL=uniform-store.js.map