typegpu
Version:
A thin layer between JS and WebGPU/WGSL that improves development experience and allows for faster iteration.
134 lines (133 loc) • 6.02 kB
JavaScript
import { mat2x2f, mat3x3f, mat4x4f } from "../../data/matrix.js";
import { f32, i32, u32 } from "../../data/numeric.js";
import { sizeOf } from "../../data/sizeOf.js";
import { vec2b, vec2f, vec2h, vec2i, vec2u, vec3b, vec3f, vec3h, vec3i, vec3u, vec4b, vec4f, vec4h, vec4i, vec4u, } from "../../data/vector.js";
import { isWgslArray, isWgslData, isWgslStruct, } from "../../data/wgslTypes.js";
import { niceStringify } from "../../shared/stringify.js";
import { bitcast } from "../../std/bitcast.js";
import { unpack2x16float } from "../../std/packing.js";
import { logger } from "../../tgpuLogger.js";
const toF = (n) => bitcast(u32, f32)(n ?? 0);
const toI = (n) => bitcast(u32, i32)(n ?? 0);
const unpack = (n) => unpack2x16float(n ?? 0);
const deserializerMap = {
f32: (d) => toF(d[0]),
f16: (d) => unpack(d[0]).x,
i32: (d) => toI(d[0]),
u32: (d) => d[0] ?? 0,
bool: (d) => !!d[0],
vec2f: (d) => vec2f(toF(d[0]), toF(d[1])),
vec3f: (d) => vec3f(toF(d[0]), toF(d[1]), toF(d[2])),
vec4f: (d) => vec4f(toF(d[0]), toF(d[1]), toF(d[2]), toF(d[3])),
vec2h(d) {
const xyVec = unpack(d[0]);
return vec2h(xyVec.x, xyVec.y);
},
vec3h(d) {
const xyVec = unpack(d[0]);
const zVec = unpack(d[1]);
return vec3h(xyVec.x, xyVec.y, zVec.x);
},
vec4h(d) {
const xyVec = unpack(d[0]);
const zwVec = unpack(d[1]);
return vec4h(xyVec.x, xyVec.y, zwVec.x, zwVec.y);
},
vec2i: (d) => vec2i(toI(d[0]), toI(d[1])),
vec3i: (d) => vec3i(toI(d[0]), toI(d[1]), toI(d[2])),
vec4i: (d) => vec4i(toI(d[0]), toI(d[1]), toI(d[2]), toI(d[3])),
vec2u: (d) => vec2u(d[0] ?? 0, d[1] ?? 0),
vec3u: (d) => vec3u(d[0] ?? 0, d[1] ?? 0, d[2] ?? 0),
vec4u: (d) => vec4u(d[0] ?? 0, d[1] ?? 0, d[2] ?? 0, d[3] ?? 0),
'vec2<bool>': (d) => vec2b(!!d[0], !!d[1]),
'vec3<bool>': (d) => vec3b(!!d[0], !!d[1], !!d[2]),
'vec4<bool>': (d) => vec4b(!!d[0], !!d[1], !!d[2], !!d[3]),
mat2x2f: (d) => mat2x2f(toF(d[0]), toF(d[1]), toF(d[2]), toF(d[3])),
mat3x3f: (d) =>
// oxfmt-ignore
mat3x3f(toF(d[0]), toF(d[1]), toF(d[2]), toF(d[4]), toF(d[5]), toF(d[6]), toF(d[8]), toF(d[9]), toF(d[10])),
mat4x4f: (d) =>
// oxfmt-ignore
mat4x4f(toF(d[0]), toF(d[1]), toF(d[2]), toF(d[3]), toF(d[4]), toF(d[5]), toF(d[6]), toF(d[7]), toF(d[8]), toF(d[9]), toF(d[10]), toF(d[11]), toF(d[12]), toF(d[13]), toF(d[14]), toF(d[15])),
};
// -------
// Helpers
// -------
/**
* Deserializes binary data from a Uint32Array into a JavaScript value based on the provided WGSL data type.
*
* @param data - The binary data as a Uint32Array to be deserialized
* @param dataType - The WGSL data type specification that determines how to interpret the binary data
*/
function deserialize(data, dataType) {
const maybeDeserializer = deserializerMap[dataType.type];
if (maybeDeserializer) {
return maybeDeserializer(data);
}
if (isWgslStruct(dataType)) {
const props = Object.keys(dataType.propTypes);
const propTypes = Object.values(dataType.propTypes);
const decodedProps = deserializeCompound(data, propTypes);
return Object.fromEntries(props.map((key, index) => [key, decodedProps[index]]));
}
if (isWgslArray(dataType)) {
const elementType = dataType.elementType;
const length = dataType.elementCount;
const result = deserializeCompound(data, Array.from({ length }, () => elementType));
return result;
}
throw new Error(`Cannot deserialize data of type ${dataType.type}`);
}
/**
* Deserializes a list of elements from a Uint32Array buffer using provided type information.
* If there is a string value among the type information, it is returned as is.
*
* @param data - The Uint32Array buffer containing the serialized data
* @param dataTypes - The WGSL data type specification that determines how to interpret the binary data, or string literals
*/
function deserializeCompound(data, dataTypes) {
let index = 0;
return dataTypes.map((info) => {
if (!isWgslData(info)) {
return info;
}
const size = Math.ceil(sizeOf(info) / 4);
const value = deserialize(data.subarray(index, index + size), info);
index += size;
return value;
});
}
export function deserializeAndStringify(serializedData, argTypes) {
return deserializeCompound(serializedData, argTypes).map(niceStringify);
}
/**
* Reads and deserializes log data from GPU buffers, logging results to the console.
*
* @remarks
* - Log entries with IDs equal to 0 are filtered out.
* - Console messages are prepended with options.messagePrefix styled with purple background and white text.
* - A warning is displayed if the log count exceeds the limit passed in options.
* - After processing, the index buffer and the data buffer are cleared.
*/
export function logDataFromGPU(resources) {
const { indexBuffer, dataBuffer, logIdToMeta, options } = resources;
void dataBuffer.read().then((data) => {
data
.filter((e) => e.id)
.forEach(({ id, serializedData }) => {
const { argTypes, op } = logIdToMeta.get(id);
const results = deserializeAndStringify(new Uint32Array(serializedData), argTypes);
if (results.length === 0) {
results.push('');
}
op.bind(console)(`%c${options.messagePrefix}%c ${results[0]}`, 'background: #936ff5; color: white;', 'color: inherit; background: none', ...results.slice(1));
});
});
void indexBuffer.read().then((totalCalls) => {
if (totalCalls > options.logCountLimit) {
logger.warn('log-limit-exceeded', `Log count limit per dispatch (${options.logCountLimit}) exceeded by ${totalCalls - options.logCountLimit} calls. Consider increasing the limit by passing appropriate options to tgpu.init().`);
}
});
dataBuffer.buffer.clear();
indexBuffer.buffer.clear();
}