@luma.gl/core
Version:
The luma.gl core Device API
185 lines • 7.35 kB
JavaScript
// luma.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import { getScratchArrayBuffer } from "../utils/array-utils-flat.js";
import { isNumberArray } from "../utils/is-array.js";
import { log } from "../utils/log.js";
import { getLeafLayoutInfo, isCompositeShaderTypeStruct } from "../shadertypes/shader-types/shader-block-layout.js";
/**
* Serializes nested JavaScript uniform values according to a {@link ShaderBlockLayout}.
*/
export class ShaderBlockWriter {
/** Layout metadata used to flatten and serialize values. */
layout;
/**
* Creates a writer for a precomputed shader-block layout.
*/
constructor(layout) {
this.layout = layout;
}
/**
* Returns `true` if the flattened layout contains the given field.
*/
has(name) {
return Boolean(this.layout.fields[name]);
}
/**
* Returns offset and size metadata for a flattened field.
*/
get(name) {
const entry = this.layout.fields[name];
return entry ? { offset: entry.offset, size: entry.size } : undefined;
}
/**
* Flattens nested composite values into leaf-path values understood by {@link UniformBlock}.
*
* Top-level values may be supplied either in nested object form matching the
* declared composite shader types or as already-flattened leaf-path values.
*/
getFlatUniformValues(uniformValues) {
const flattenedUniformValues = {};
for (const [name, value] of Object.entries(uniformValues)) {
const uniformType = this.layout.uniformTypes[name];
if (uniformType) {
this._flattenCompositeValue(flattenedUniformValues, name, uniformType, value);
}
else if (this.layout.fields[name]) {
flattenedUniformValues[name] = value;
}
}
return flattenedUniformValues;
}
/**
* Serializes the supplied values into buffer-backed binary data.
*
* The returned view length matches {@link ShaderBlockLayout.byteLength}, which
* is the exact packed size of the block.
*/
getData(uniformValues) {
const buffer = getScratchArrayBuffer(this.layout.byteLength);
new Uint8Array(buffer, 0, this.layout.byteLength).fill(0);
const typedArrays = {
i32: new Int32Array(buffer),
u32: new Uint32Array(buffer),
f32: new Float32Array(buffer),
f16: new Uint16Array(buffer)
};
const flattenedUniformValues = this.getFlatUniformValues(uniformValues);
for (const [name, value] of Object.entries(flattenedUniformValues)) {
this._writeLeafValue(typedArrays, name, value);
}
return new Uint8Array(buffer, 0, this.layout.byteLength);
}
/**
* Recursively flattens nested values using the declared composite shader type.
*/
_flattenCompositeValue(flattenedUniformValues, baseName, uniformType, value) {
if (value === undefined) {
return;
}
if (typeof uniformType === 'string' || this.layout.fields[baseName]) {
flattenedUniformValues[baseName] = value;
return;
}
if (Array.isArray(uniformType)) {
const elementType = uniformType[0];
const length = uniformType[1];
if (Array.isArray(elementType)) {
throw new Error(`Nested arrays are not supported for ${baseName}`);
}
if (typeof elementType === 'string' && isNumberArray(value)) {
this._flattenPackedArray(flattenedUniformValues, baseName, elementType, length, value);
return;
}
if (!Array.isArray(value)) {
log.warn(`Unsupported uniform array value for ${baseName}:`, value)();
return;
}
for (let index = 0; index < Math.min(value.length, length); index++) {
const elementValue = value[index];
if (elementValue === undefined) {
continue;
}
this._flattenCompositeValue(flattenedUniformValues, `${baseName}[${index}]`, elementType, elementValue);
}
return;
}
if (isCompositeShaderTypeStruct(uniformType) && isCompositeUniformObject(value)) {
for (const [key, subValue] of Object.entries(value)) {
if (subValue === undefined) {
continue;
}
const nestedName = `${baseName}.${key}`;
this._flattenCompositeValue(flattenedUniformValues, nestedName, uniformType[key], subValue);
}
return;
}
log.warn(`Unsupported uniform value for ${baseName}:`, value)();
}
/**
* Expands tightly packed numeric arrays into per-element leaf fields.
*/
_flattenPackedArray(flattenedUniformValues, baseName, elementType, length, value) {
const numericValue = value;
const elementLayout = getLeafLayoutInfo(elementType, this.layout.layout);
const packedElementLength = elementLayout.components;
for (let index = 0; index < length; index++) {
const start = index * packedElementLength;
if (start >= numericValue.length) {
break;
}
if (packedElementLength === 1) {
flattenedUniformValues[`${baseName}[${index}]`] = Number(numericValue[start]);
}
else {
flattenedUniformValues[`${baseName}[${index}]`] = sliceNumericArray(value, start, start + packedElementLength);
}
}
}
/**
* Writes one flattened leaf value into its typed-array view.
*/
_writeLeafValue(typedArrays, name, value) {
const entry = this.layout.fields[name];
if (!entry) {
log.warn(`Uniform ${name} not found in layout`)();
return;
}
const { type, components, columns, rows, offset, columnStride } = entry;
const array = typedArrays[type];
if (components === 1) {
array[offset] = Number(value);
return;
}
const sourceValue = value;
if (columns === 1) {
for (let componentIndex = 0; componentIndex < components; componentIndex++) {
array[offset + componentIndex] = Number(sourceValue[componentIndex] ?? 0);
}
return;
}
let sourceIndex = 0;
for (let columnIndex = 0; columnIndex < columns; columnIndex++) {
const columnOffset = offset + columnIndex * columnStride;
for (let rowIndex = 0; rowIndex < rows; rowIndex++) {
array[columnOffset + rowIndex] = Number(sourceValue[sourceIndex++] ?? 0);
}
}
}
}
/**
* Type guard for nested uniform objects.
*/
function isCompositeUniformObject(value) {
return (Boolean(value) &&
typeof value === 'object' &&
!Array.isArray(value) &&
!ArrayBuffer.isView(value));
}
/**
* Slices a numeric array-like value without changing its numeric representation.
*/
function sliceNumericArray(value, start, end) {
return Array.prototype.slice.call(value, start, end);
}
//# sourceMappingURL=shader-block-writer.js.map