mirror of
https://github.com/Rezmason/matrix.git
synced 2026-04-14 12:29:30 -07:00
Moved the WebGPU code off of "std140" and onto gpu-uniforms.
This commit is contained in:
4
TODO.txt
4
TODO.txt
@@ -10,9 +10,9 @@ WebGPU
|
|||||||
|
|
||||||
Try to change post processing to compute shaders once they're easier to support
|
Try to change post processing to compute shaders once they're easier to support
|
||||||
|
|
||||||
buffer-stuffer (was "std140")
|
gpu-uniforms (was "std140")
|
||||||
Resolve the memory positions of the fields in the parse layouts
|
Resolve the memory positions of the fields in the parse layouts
|
||||||
Resolve each layout into a Proxy around an ArrayBuffer
|
Resolve each layout into a Proxy around an ArrayBuffer and three mapped typedarrays
|
||||||
Document and share it
|
Document and share it
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import std140 from "./std140.js";
|
import uniforms from "/lib/gpu-uniforms.js";
|
||||||
import { loadTexture, loadShader, makeUniformBuffer, makePassFBO, makePass } from "./utils.js";
|
import { loadTexture, loadShader, makeUniformBuffer, makePassFBO, makePass } from "./utils.js";
|
||||||
|
|
||||||
// Multiplies the rendered rain and bloom by a loaded in image
|
// Multiplies the rendered rain and bloom by a loaded in image
|
||||||
@@ -10,9 +10,6 @@ export default (context, getInputs) => {
|
|||||||
const { config, adapter, device, canvasContext } = context;
|
const { config, adapter, device, canvasContext } = context;
|
||||||
const ditherMagnitude = 0.05;
|
const ditherMagnitude = 0.05;
|
||||||
|
|
||||||
const configLayout = std140(["f32", "vec3<f32>"]);
|
|
||||||
const configBuffer = makeUniformBuffer(device, configLayout, [ditherMagnitude, config.backgroundColor]);
|
|
||||||
|
|
||||||
const linearSampler = device.createSampler({
|
const linearSampler = device.createSampler({
|
||||||
magFilter: "linear",
|
magFilter: "linear",
|
||||||
minFilter: "linear",
|
minFilter: "linear",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import std140 from "./std140.js";
|
import uniforms from "/lib/gpu-uniforms.js";
|
||||||
import { getCanvasSize, makeUniformBuffer, makePipeline } from "./utils.js";
|
import { getCanvasSize, makeUniformBuffer, makePipeline } from "./utils.js";
|
||||||
|
|
||||||
import makeRain from "./rainPass.js";
|
import makeRain from "./rainPass.js";
|
||||||
@@ -38,8 +38,8 @@ export default async (canvas, config) => {
|
|||||||
GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST,
|
GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST,
|
||||||
};
|
};
|
||||||
|
|
||||||
const timeLayout = std140(["f32", "i32"]);
|
const timeUniforms = uniforms.read(`[[block]] struct Time { seconds : f32; frames : i32; };`).Time;
|
||||||
const timeBuffer = makeUniformBuffer(device, timeLayout);
|
const timeBuffer = makeUniformBuffer(device, timeUniforms);
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
config,
|
config,
|
||||||
@@ -54,7 +54,7 @@ export default async (canvas, config) => {
|
|||||||
|
|
||||||
await Promise.all(pipeline.map((step) => step.ready));
|
await Promise.all(pipeline.map((step) => step.ready));
|
||||||
|
|
||||||
let frame = 0;
|
let frames = 0;
|
||||||
let start = NaN;
|
let start = NaN;
|
||||||
|
|
||||||
const renderLoop = (now) => {
|
const renderLoop = (now) => {
|
||||||
@@ -68,8 +68,8 @@ export default async (canvas, config) => {
|
|||||||
pipeline.forEach((step) => step.setSize(...canvasSize));
|
pipeline.forEach((step) => step.setSize(...canvasSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
device.queue.writeBuffer(timeBuffer, 0, timeLayout.build([(now - start) / 1000, frame]));
|
device.queue.writeBuffer(timeBuffer, 0, timeUniforms.write({ seconds: (now - start) / 1000, frames }));
|
||||||
frame++;
|
frames++;
|
||||||
|
|
||||||
const encoder = device.createCommandEncoder();
|
const encoder = device.createCommandEncoder();
|
||||||
pipeline.forEach((step) => step.execute(encoder));
|
pipeline.forEach((step) => step.execute(encoder));
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import std140 from "./std140.js";
|
import uniforms from "/lib/gpu-uniforms.js";
|
||||||
import { loadShader, makeUniformBuffer, makePassFBO, makePass } from "./utils.js";
|
import { loadShader, makeUniformBuffer, makePassFBO, makePass } from "./utils.js";
|
||||||
|
|
||||||
// Maps the brightness of the rendered rain and bloom to colors
|
// Maps the brightness of the rendered rain and bloom to colors
|
||||||
@@ -53,7 +53,7 @@ const makePalette = (device, entries) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: support arrays in std140
|
// TODO: try using gpu-uniforms
|
||||||
|
|
||||||
const paletteBuffer = device.createBuffer({
|
const paletteBuffer = device.createBuffer({
|
||||||
size: (3 + 1) * PALETTE_SIZE * Float32Array.BYTES_PER_ELEMENT,
|
size: (3 + 1) * PALETTE_SIZE * Float32Array.BYTES_PER_ELEMENT,
|
||||||
@@ -81,8 +81,8 @@ export default (context, getInputs) => {
|
|||||||
const { config, adapter, device, canvasContext, timeBuffer } = context;
|
const { config, adapter, device, canvasContext, timeBuffer } = context;
|
||||||
const ditherMagnitude = 0.05;
|
const ditherMagnitude = 0.05;
|
||||||
|
|
||||||
const configLayout = std140(["f32", "vec3<f32>"]);
|
const configUniforms = uniforms.read(`struct Config { ditherMagnitude : f32; backgroundColor: vec3<f32>; };`).Config;
|
||||||
const configBuffer = makeUniformBuffer(device, configLayout, [ditherMagnitude, config.backgroundColor]);
|
const configBuffer = makeUniformBuffer(device, configUniforms, { ditherMagnitude, backgroundColor: config.backgroundColor });
|
||||||
|
|
||||||
const paletteBuffer = makePalette(device, config.paletteEntries);
|
const paletteBuffer = makePalette(device, config.paletteEntries);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import std140 from "./std140.js";
|
import uniforms from "/lib/gpu-uniforms.js";
|
||||||
import { makePassFBO, loadTexture, loadShader, makeUniformBuffer, makePass } from "./utils.js";
|
import { makePassFBO, loadTexture, loadShader, makeUniformBuffer, makePass } from "./utils.js";
|
||||||
|
|
||||||
const { mat4, vec3 } = glMatrix;
|
const { mat4, vec3 } = glMatrix;
|
||||||
@@ -15,58 +15,20 @@ const cycleStyles = {
|
|||||||
|
|
||||||
const numVerticesPerQuad = 2 * 3;
|
const numVerticesPerQuad = 2 * 3;
|
||||||
|
|
||||||
const makeConfigBuffer = (device, config, density, gridSize) => {
|
const makeConfigBuffer = (device, configUniforms, config, density, gridSize) => {
|
||||||
// Various effect-related values
|
const configData = {
|
||||||
const rippleType = config.rippleTypeName in rippleTypes ? rippleTypes[config.rippleTypeName] : -1;
|
...config,
|
||||||
const cycleStyle = config.cycleStyleName in cycleStyles ? cycleStyles[config.cycleStyleName] : 0;
|
gridSize,
|
||||||
const slantVec = [Math.cos(config.slant), Math.sin(config.slant)];
|
density,
|
||||||
const slantScale = 1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1);
|
showComputationTexture: config.effect === "none",
|
||||||
const showComputationTexture = config.effect === "none";
|
cycleStyle: config.cycleStyleName in cycleStyles ? cycleStyles[config.cycleStyleName] : 0,
|
||||||
|
rippleType: config.rippleTypeName in rippleTypes ? rippleTypes[config.rippleTypeName] : -1,
|
||||||
const configData = [
|
slantScale: 1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1),
|
||||||
// common
|
slantVec: [Math.cos(config.slant), Math.sin(config.slant)],
|
||||||
{ name: "animationSpeed", type: "f32", value: config.animationSpeed },
|
};
|
||||||
{ name: "glyphSequenceLength", type: "i32", value: config.glyphSequenceLength },
|
|
||||||
{ name: "glyphTextureColumns", type: "i32", value: config.glyphTextureColumns },
|
|
||||||
{ name: "glyphHeightToWidth", type: "f32", value: config.glyphHeightToWidth },
|
|
||||||
{ name: "resurrectingCodeRatio", type: "f32", value: config.resurrectingCodeRatio },
|
|
||||||
{ name: "gridSize", type: "vec2<f32>", value: gridSize },
|
|
||||||
{ name: "showComputationTexture", type: "i32", value: showComputationTexture },
|
|
||||||
|
|
||||||
// compute
|
|
||||||
{ name: "brightnessThreshold", type: "f32", value: config.brightnessThreshold },
|
|
||||||
{ name: "brightnessOverride", type: "f32", value: config.brightnessOverride },
|
|
||||||
{ name: "brightnessDecay", type: "f32", value: config.brightnessDecay },
|
|
||||||
{ name: "cursorEffectThreshold", type: "f32", value: config.cursorEffectThreshold },
|
|
||||||
{ name: "cycleSpeed", type: "f32", value: config.cycleSpeed },
|
|
||||||
{ name: "cycleFrameSkip", type: "i32", value: config.cycleFrameSkip },
|
|
||||||
{ name: "fallSpeed", type: "f32", value: config.fallSpeed },
|
|
||||||
{ name: "hasSun", type: "i32", value: config.hasSun },
|
|
||||||
{ name: "hasThunder", type: "i32", value: config.hasThunder },
|
|
||||||
{ name: "raindropLength", type: "f32", value: config.raindropLength },
|
|
||||||
{ name: "rippleScale", type: "f32", value: config.rippleScale },
|
|
||||||
{ name: "rippleSpeed", type: "f32", value: config.rippleSpeed },
|
|
||||||
{ name: "rippleThickness", type: "f32", value: config.rippleThickness },
|
|
||||||
{ name: "cycleStyle", type: "i32", value: cycleStyle },
|
|
||||||
{ name: "rippleType", type: "i32", value: rippleType },
|
|
||||||
|
|
||||||
// render
|
|
||||||
{ name: "forwardSpeed", type: "f32", value: config.forwardSpeed },
|
|
||||||
{ name: "glyphVerticalSpacing", type: "f32", value: config.glyphVerticalSpacing },
|
|
||||||
{ name: "glyphEdgeCrop", type: "f32", value: config.glyphEdgeCrop },
|
|
||||||
{ name: "isPolar", type: "i32", value: config.isPolar },
|
|
||||||
{ name: "density", type: "f32", value: density },
|
|
||||||
{ name: "slantScale", type: "f32", value: slantScale },
|
|
||||||
{ name: "slantVec", type: "vec2<f32>", value: slantVec },
|
|
||||||
{ name: "volumetric", type: "i32", value: config.volumetric },
|
|
||||||
];
|
|
||||||
console.table(configData);
|
console.table(configData);
|
||||||
|
|
||||||
return makeUniformBuffer(
|
return makeUniformBuffer(device, configUniforms, configData);
|
||||||
device,
|
|
||||||
std140(configData.map((field) => field.type)),
|
|
||||||
configData.map((field) => field.value)
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (context, getInputs) => {
|
export default (context, getInputs) => {
|
||||||
@@ -84,13 +46,10 @@ export default (context, getInputs) => {
|
|||||||
// rather than a single quad for our geometry
|
// rather than a single quad for our geometry
|
||||||
const numQuads = config.volumetric ? numCells : 1;
|
const numQuads = config.volumetric ? numCells : 1;
|
||||||
|
|
||||||
const configBuffer = makeConfigBuffer(device, config, density, gridSize);
|
// TODO: uniforms should be updated to provide this too
|
||||||
|
|
||||||
const sceneLayout = std140(["vec2<f32>", "mat4x4<f32>", "mat4x4<f32>"]);
|
|
||||||
const sceneBuffer = makeUniformBuffer(device, sceneLayout);
|
|
||||||
|
|
||||||
const cellsBuffer = device.createBuffer({
|
const cellsBuffer = device.createBuffer({
|
||||||
size: numCells * std140(["vec4<f32>"]).size,
|
size: numCells * 4 * Float32Array.BYTES_PER_ELEMENT,
|
||||||
usage: GPUBufferUsage.STORAGE,
|
usage: GPUBufferUsage.STORAGE,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -115,6 +74,9 @@ export default (context, getInputs) => {
|
|||||||
|
|
||||||
const presentationFormat = canvasContext.getPreferredFormat(adapter);
|
const presentationFormat = canvasContext.getPreferredFormat(adapter);
|
||||||
|
|
||||||
|
let configBuffer;
|
||||||
|
let sceneUniforms;
|
||||||
|
let sceneBuffer;
|
||||||
let computePipeline;
|
let computePipeline;
|
||||||
let renderPipeline;
|
let renderPipeline;
|
||||||
let computeBindGroup;
|
let computeBindGroup;
|
||||||
@@ -124,6 +86,12 @@ export default (context, getInputs) => {
|
|||||||
const ready = (async () => {
|
const ready = (async () => {
|
||||||
const [msdfTexture, rainShader] = await Promise.all(assets);
|
const [msdfTexture, rainShader] = await Promise.all(assets);
|
||||||
|
|
||||||
|
const rainShaderUniforms = uniforms.read(rainShader.code);
|
||||||
|
configBuffer = makeConfigBuffer(device, rainShaderUniforms.Config, config, density, gridSize);
|
||||||
|
|
||||||
|
sceneUniforms = rainShaderUniforms.Scene;
|
||||||
|
sceneBuffer = makeUniformBuffer(device, sceneUniforms);
|
||||||
|
|
||||||
computePipeline = device.createComputePipeline({
|
computePipeline = device.createComputePipeline({
|
||||||
compute: {
|
compute: {
|
||||||
module: rainShader.module,
|
module: rainShader.module,
|
||||||
@@ -183,7 +151,7 @@ export default (context, getInputs) => {
|
|||||||
const aspectRatio = width / height;
|
const aspectRatio = width / height;
|
||||||
mat4.perspectiveZO(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000);
|
mat4.perspectiveZO(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000);
|
||||||
const screenSize = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1];
|
const screenSize = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1];
|
||||||
device.queue.writeBuffer(sceneBuffer, 0, sceneLayout.build([screenSize, camera, transform]));
|
device.queue.writeBuffer(sceneBuffer, 0, sceneUniforms.write({ screenSize, camera, transform }));
|
||||||
|
|
||||||
// Update
|
// Update
|
||||||
output?.destroy();
|
output?.destroy();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import std140 from "./std140.js";
|
import uniforms from "/lib/gpu-uniforms.js";
|
||||||
import { loadShader, makeUniformBuffer, makePassFBO, makePass } from "./utils.js";
|
import { loadShader, makeUniformBuffer, makePassFBO, makePass } from "./utils.js";
|
||||||
|
|
||||||
// Matrix Resurrections isn't in theaters yet,
|
// Matrix Resurrections isn't in theaters yet,
|
||||||
@@ -15,8 +15,8 @@ export default (context, getInputs) => {
|
|||||||
const { config, adapter, device, canvasContext, timeBuffer } = context;
|
const { config, adapter, device, canvasContext, timeBuffer } = context;
|
||||||
const ditherMagnitude = 0.05;
|
const ditherMagnitude = 0.05;
|
||||||
|
|
||||||
const configLayout = std140(["f32", "vec3<f32>"]);
|
const configUniforms = uniforms.read(`struct Config { ditherMagnitude : f32; backgroundColor: vec3<f32>; };`).Config;
|
||||||
const configBuffer = makeUniformBuffer(device, configLayout, [ditherMagnitude, config.backgroundColor]);
|
const configBuffer = makeUniformBuffer(device, configUniforms, { ditherMagnitude, backgroundColor: config.backgroundColor });
|
||||||
|
|
||||||
const linearSampler = device.createSampler({
|
const linearSampler = device.createSampler({
|
||||||
magFilter: "linear",
|
magFilter: "linear",
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
const supportedTypes = {
|
|
||||||
["i32"]: [1, 1, "i32"],
|
|
||||||
["u32"]: [1, 1, "u32"],
|
|
||||||
["f32"]: [1, 1, "f32"],
|
|
||||||
|
|
||||||
["atomic<i32>"]: [1, 1, "i32"],
|
|
||||||
["vec2<i32>"]: [2, 2, "i32"],
|
|
||||||
["vec3<i32>"]: [4, 3, "i32"],
|
|
||||||
["vec4<i32>"]: [4, 4, "i32"],
|
|
||||||
|
|
||||||
["atomic<u32>"]: [1, 1, "u32"],
|
|
||||||
["vec2<u32>"]: [2, 2, "u32"],
|
|
||||||
["vec3<u32>"]: [4, 3, "u32"],
|
|
||||||
["vec4<u32>"]: [4, 4, "u32"],
|
|
||||||
|
|
||||||
["atomic<f32>"]: [1, 1, "f32"],
|
|
||||||
["vec2<f32>"]: [2, 2, "f32"],
|
|
||||||
["vec3<f32>"]: [4, 3, "f32"],
|
|
||||||
["vec4<f32>"]: [4, 4, "f32"],
|
|
||||||
|
|
||||||
["mat2x2<f32>"]: [2, 4, "f32"],
|
|
||||||
["mat3x2<f32>"]: [2, 6, "f32"],
|
|
||||||
["mat4x2<f32>"]: [2, 8, "f32"],
|
|
||||||
["mat2x3<f32>"]: [4, 8, "f32"],
|
|
||||||
["mat3x3<f32>"]: [4, 12, "f32"],
|
|
||||||
["mat4x3<f32>"]: [4, 16, "f32"],
|
|
||||||
["mat2x4<f32>"]: [4, 8, "f32"],
|
|
||||||
["mat3x4<f32>"]: [4, 12, "f32"],
|
|
||||||
["mat4x4<f32>"]: [4, 16, "f32"],
|
|
||||||
};
|
|
||||||
|
|
||||||
const computeStructLayout = (types) => {
|
|
||||||
const fields = [];
|
|
||||||
let byteOffset = 0;
|
|
||||||
for (const type of types) {
|
|
||||||
if (supportedTypes[type] == null) {
|
|
||||||
throw new Error(`Unsupported type: ${type}`);
|
|
||||||
}
|
|
||||||
const [alignAtByte, sizeInBytes, baseType] = supportedTypes[type];
|
|
||||||
byteOffset = Math.ceil(byteOffset / alignAtByte) * alignAtByte;
|
|
||||||
fields.push({ baseType, byteOffset });
|
|
||||||
byteOffset += sizeInBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.log(types);
|
|
||||||
// console.log(fields);
|
|
||||||
|
|
||||||
const size = byteOffset * Float32Array.BYTES_PER_ELEMENT;
|
|
||||||
|
|
||||||
return {
|
|
||||||
fields,
|
|
||||||
size,
|
|
||||||
build: (values, buffer = null) => buildStruct(fields, values, buffer ?? new ArrayBuffer(size)),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const buildStruct = (fields, values, buffer) => {
|
|
||||||
if (values.length !== fields.length) {
|
|
||||||
throw new Error(`This struct contains ${fields.length} values, and you supplied ${values.length}.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const views = {
|
|
||||||
i32: new Int32Array(buffer),
|
|
||||||
u32: new Uint32Array(buffer),
|
|
||||||
f32: new Float32Array(buffer),
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let i = 0; i < values.length; i++) {
|
|
||||||
const view = views[fields[i].baseType];
|
|
||||||
const value = values[i];
|
|
||||||
const array = value[Symbol.iterator] == null ? [Number(value)] : value;
|
|
||||||
view.set(array, fields[i].byteOffset);
|
|
||||||
}
|
|
||||||
return buffer;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default computeStructLayout;
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import std140 from "./std140.js";
|
import uniforms from "/lib/gpu-uniforms.js";
|
||||||
import { loadShader, make1DTexture, makeUniformBuffer, makePassFBO, makePass } from "./utils.js";
|
import { loadShader, make1DTexture, makeUniformBuffer, makePassFBO, makePass } from "./utils.js";
|
||||||
|
|
||||||
// Multiplies the rendered rain and bloom by a 1D gradient texture
|
// Multiplies the rendered rain and bloom by a 1D gradient texture
|
||||||
@@ -41,8 +41,8 @@ export default (context, getInputs) => {
|
|||||||
const { config, adapter, device, canvasContext, timeBuffer } = context;
|
const { config, adapter, device, canvasContext, timeBuffer } = context;
|
||||||
const ditherMagnitude = 0.05;
|
const ditherMagnitude = 0.05;
|
||||||
|
|
||||||
const configLayout = std140(["f32", "vec3<f32>"]);
|
const configUniforms = uniforms.read(`struct Config { ditherMagnitude : f32; backgroundColor: vec3<f32>; };`).Config;
|
||||||
const configBuffer = makeUniformBuffer(device, configLayout, [ditherMagnitude, config.backgroundColor]);
|
const configBuffer = makeUniformBuffer(device, configUniforms, { ditherMagnitude, backgroundColor: config.backgroundColor });
|
||||||
|
|
||||||
// Expand and convert stripe colors into 1D texture data
|
// Expand and convert stripe colors into 1D texture data
|
||||||
const stripeColors =
|
const stripeColors =
|
||||||
|
|||||||
@@ -44,14 +44,14 @@ const loadShader = async (device, url) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeUniformBuffer = (device, structLayout, values = null) => {
|
const makeUniformBuffer = (device, uniforms, data = null) => {
|
||||||
const buffer = device.createBuffer({
|
const buffer = device.createBuffer({
|
||||||
size: structLayout.size,
|
size: uniforms.minSize,
|
||||||
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
||||||
mappedAtCreation: values != null,
|
mappedAtCreation: data != null,
|
||||||
});
|
});
|
||||||
if (values != null) {
|
if (data != null) {
|
||||||
structLayout.build(values, buffer.getMappedRange());
|
uniforms.write(data, buffer.getMappedRange());
|
||||||
buffer.unmap();
|
buffer.unmap();
|
||||||
}
|
}
|
||||||
return buffer;
|
return buffer;
|
||||||
|
|||||||
@@ -166,6 +166,10 @@ const parseStructLayoutsFromShader = (wgsl) => {
|
|||||||
const makeDataForLayout = (structLayouts, layout) => Object.fromEntries(layout.fields.map((field) => [field.identifier, field.defaultValue()]));
|
const makeDataForLayout = (structLayouts, layout) => Object.fromEntries(layout.fields.map((field) => [field.identifier, field.defaultValue()]));
|
||||||
|
|
||||||
const writeField = (allLayouts, field, value, views, byteOffset) => {
|
const writeField = (allLayouts, field, value, views, byteOffset) => {
|
||||||
|
if (value == null) {
|
||||||
|
console.warn(`Property missing: ${field.identifier}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (field.isArray) {
|
if (field.isArray) {
|
||||||
const count = field.isFixedSize ? field.mult : value.length;
|
const count = field.isFixedSize ? field.mult : value.length;
|
||||||
for (let i = 0; i < field.mult; i++) {
|
for (let i = 0; i < field.mult; i++) {
|
||||||
@@ -182,46 +186,34 @@ const writeField = (allLayouts, field, value, views, byteOffset) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class Uniforms {
|
const makeGenerator = (layout, structLayouts) => {
|
||||||
static fromWGSL(wgsl) {
|
const minSize = layout.sizeInBytes;
|
||||||
|
return Object.freeze({
|
||||||
|
minSize,
|
||||||
|
create: () => makeDataForLayout(structLayouts, layout),
|
||||||
|
write: (object, destination) => {
|
||||||
|
destination ??= new ArrayBuffer(layout.sizeInBytes); // TODO: expand to support runtime-sized arrays, via the length of the array on the data object
|
||||||
|
|
||||||
|
const views = {
|
||||||
|
i32: new Int32Array(destination),
|
||||||
|
u32: new Uint32Array(destination),
|
||||||
|
f32: new Float32Array(destination),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const field of layout.fields) {
|
||||||
|
writeField(structLayouts, field, object[field.identifier], views, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return destination;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const api = Object.freeze({
|
||||||
|
read: (wgsl) => {
|
||||||
const structLayouts = parseStructLayoutsFromShader(wgsl);
|
const structLayouts = parseStructLayoutsFromShader(wgsl);
|
||||||
return Object.fromEntries(Object.entries(structLayouts).map(([name, layout]) => [name, new Uniforms(layout, structLayouts)]));
|
return Object.fromEntries(Object.entries(structLayouts).map(([name, layout]) => [name, makeGenerator(layout, structLayouts)]));
|
||||||
}
|
},
|
||||||
|
});
|
||||||
|
|
||||||
#structLayouts;
|
export default api;
|
||||||
#layout;
|
|
||||||
data;
|
|
||||||
minSize;
|
|
||||||
|
|
||||||
constructor(layout, structLayouts = null) {
|
|
||||||
if (typeof layout === "string") {
|
|
||||||
structLayouts = parseStructLayoutsFromShader(layout);
|
|
||||||
layout = Object.values(structLayouts)[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
structLayouts ??= {};
|
|
||||||
this.#structLayouts = structLayouts;
|
|
||||||
this.#layout = layout;
|
|
||||||
this.minSize = layout.sizeInBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
object() {
|
|
||||||
return makeDataForLayout(this.#structLayouts, this.#layout);
|
|
||||||
}
|
|
||||||
|
|
||||||
stuff(object, destination) {
|
|
||||||
destination ??= new ArrayBuffer(this.#layout.sizeInBytes); // TODO: expand to support runtime-sized arrays, via the length of the array on the data object
|
|
||||||
|
|
||||||
const views = {
|
|
||||||
i32: new Int32Array(destination),
|
|
||||||
u32: new Uint32Array(destination),
|
|
||||||
f32: new Float32Array(destination),
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const field of this.#layout.fields) {
|
|
||||||
writeField(this.#structLayouts, field, object[field.identifier], views, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return destination;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user