Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 87 additions & 132 deletions engine/playground-render-canvas/src/RenderCanvas.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { ComputePipeline } from './compute';
import { GraphicsPipeline, passThroughshaderCode } from './pass_through';
import { NotReadyError, parsePrintfBuffer, sizeFromFormat } from './canvasUtils';
import type { Bindings, CallCommand, CompiledPlayground, ResourceCommand } from 'slang-playground-shared';
import { type ResourceCommand, type Bindings, type CallCommand, type CompiledPlayground, type UniformController, type ScalarType, getScalarSize } from 'slang-playground-shared';
import { onMounted, ref, useTemplateRef } from 'vue';

let fileUri: string;
Expand Down Expand Up @@ -347,55 +347,7 @@ async function execFrame(timeMS: number, playgroundData: CompiledPlayground, fir
throw new Error("uniformInput doesn't exist or is of incorrect type");
}

let uniformBufferData = new ArrayBuffer(playgroundData.uniformSize);
let uniformBufferView = new DataView(uniformBufferData);

for (let uniformComponent of playgroundData.uniformComponents) {
let offset = uniformComponent.buffer_offset;
if (uniformComponent.type == "SLIDER") {
uniformBufferView.setFloat32(offset, uniformComponent.value, true);
} else if (uniformComponent.type == "COLOR_PICK") {
uniformComponent.value.forEach((v, i) => {
uniformBufferView.setFloat32(offset + i * 4, v, true);
});
} else if (uniformComponent.type == "TIME") {
uniformBufferView.setFloat32(offset, timeMS * 0.001, true);
} else if (uniformComponent.type == "FRAME_ID") {
uniformBufferView.setFloat32(offset, frameID.value, true);
} else if (uniformComponent.type == "MOUSE_POSITION") {
uniformBufferView.setFloat32(offset, canvasCurrentMousePos.x, true);
uniformBufferView.setFloat32(offset + 4, canvasCurrentMousePos.y, true);
uniformBufferView.setFloat32(offset + 8, canvasLastMouseDownPos.x * (canvasIsMouseDown ? -1 : 1), true);
uniformBufferView.setFloat32(offset + 12, canvasLastMouseDownPos.y * (canvasMouseClicked ? -1 : 1), true);
} else if (uniformComponent.type == "KEY") {
// Set 1 or 0 depending on key state, using correct type
const isPressed = pressedKeys.has(uniformComponent.key);
if (uniformComponent.scalarType == "float32") {
uniformBufferView.setFloat32(offset, isPressed ? 1.0 : 0.0, true);
} else if (uniformComponent.scalarType == "float64") {
uniformBufferView.setFloat64(offset, isPressed ? 1.0 : 0.0, true);
} else if (uniformComponent.scalarType == "int8") {
uniformBufferView.setInt8(offset, isPressed ? 1 : 0);
} else if (uniformComponent.scalarType == "int16") {
uniformBufferView.setInt16(offset, isPressed ? 1 : 0, true);
} else if (uniformComponent.scalarType == "int32") {
uniformBufferView.setInt32(offset, isPressed ? 1 : 0, true);
} else if (uniformComponent.scalarType == "uint8") {
uniformBufferView.setUint8(offset, isPressed ? 1 : 0);
} else if (uniformComponent.scalarType == "uint16") {
uniformBufferView.setUint16(offset, isPressed ? 1 : 0, true);
} else if (uniformComponent.scalarType == "uint32") {
uniformBufferView.setUint32(offset, isPressed ? 1 : 0, true);
} else {
throw new Error("KEY_INPUT only scalar type not supported");
}
} else {
let _: never = uniformComponent;
throw new Error("Invalid state");
}
}

device.queue.writeBuffer(uniformInput, 0, new Uint8Array(uniformBufferData));
writeUniformData(uniformInput, playgroundData.uniformComponents, playgroundData.uniformSize, timeMS);

// Encode commands to do the computation
const encoder = device.createCommandEncoder({ label: 'compute builtin encoder' });
Expand Down Expand Up @@ -574,6 +526,76 @@ async function execFrame(timeMS: number, playgroundData: CompiledPlayground, fir
return true;
}

function writeScalar(uniformBufferView: DataView<ArrayBuffer>, scalarType: ScalarType, offset: number, value: number): boolean {
if (scalarType == "float32") {
uniformBufferView.setFloat32(offset, value, true);
} else if (scalarType == "float64") {
uniformBufferView.setFloat64(offset, value, true);
} else if (scalarType == "int8") {
uniformBufferView.setInt8(offset, value);
} else if (scalarType == "int16") {
uniformBufferView.setInt16(offset, value, true);
} else if (scalarType == "int32") {
uniformBufferView.setInt32(offset, value, true);
} else if (scalarType == "uint8") {
uniformBufferView.setUint8(offset, value);
} else if (scalarType == "uint16") {
uniformBufferView.setUint16(offset, value, true);
} else if (scalarType == "uint32") {
uniformBufferView.setUint32(offset, value, true);
} else {
return false;
}
return true;
}

function writeUniformData(uniformInput: GPUBuffer, uniformComponents: UniformController[], uniformSize: number, timeMS: number) {
let uniformBufferData = new ArrayBuffer(uniformSize);
let uniformBufferView = new DataView(uniformBufferData);

for (let uniformComponent of uniformComponents) {
let offset = uniformComponent.buffer_offset;
if (uniformComponent.type == "SLIDER") {
uniformBufferView.setFloat32(offset, uniformComponent.value, true);
} else if (uniformComponent.type == "COLOR_PICK") {
uniformComponent.value.forEach((v, i) => {
uniformBufferView.setFloat32(offset + i * 4, v, true);
});
} else if (uniformComponent.type == "TIME") {
if(!writeScalar(uniformBufferView, uniformComponent.scalarType, offset, timeMS * 0.001)) {
throw new Error(`scalar type not supported for ${uniformComponent.type} uniform`);
}
} else if (uniformComponent.type == "FRAME_ID") {
if(!writeScalar(uniformBufferView, uniformComponent.scalarType, offset, frameID.value)) {
throw new Error(`scalar type not supported for ${uniformComponent.type} uniform`);
}
} else if (uniformComponent.type == "MOUSE_POSITION") {
let data = [
canvasCurrentMousePos.x,
canvasCurrentMousePos.y,
canvasLastMouseDownPos.x * (canvasIsMouseDown ? -1 : 1),
canvasLastMouseDownPos.y * (canvasMouseClicked ? -1 : 1),
];
for (let i = 0; i < data.length; i++) {
if(!writeScalar(uniformBufferView, uniformComponent.scalarType, offset + i * getScalarSize(uniformComponent.scalarType) / 8, data[i])) {
throw new Error(`scalar type not supported for ${uniformComponent.type} uniform`);
}
}
} else if (uniformComponent.type == "KEY") {
// Set 1 or 0 depending on key state, using correct type
const isPressed = pressedKeys.has(uniformComponent.key);
if(!writeScalar(uniformBufferView, uniformComponent.scalarType, offset, isPressed ? 1 : 0)) {
throw new Error(`scalar type not supported for ${uniformComponent.type} uniform`);
}
} else {
let _: never = uniformComponent;
throw new Error("Invalid state");
}
}

device.queue.writeBuffer(uniformInput, 0, new Uint8Array(uniformBufferData));
}

function safeSet<T extends GPUObjectBase>(map: Map<string, T>, key: string, value: T) {
if (map.has(key)) {
let currentEntry = map.get(key);
Expand All @@ -587,21 +609,22 @@ function safeSet<T extends GPUObjectBase>(map: Map<string, T>, key: string, valu

async function processResourceCommands(
resourceBindings: Bindings,
resourceCommands: ResourceCommand[],
bindingResourceCommands: ResourceCommand[],
resourceMetadata: { [k: string]: ResourceMetadata },
uniformSize: number
) {
let allocatedResources: Map<string, GPUObjectBase> = new Map();

safeSet(allocatedResources, "uniformInput", device.createBuffer({ size: uniformSize, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST }));

for (const { resourceName, parsedCommand } of resourceCommands) {
for (const { resourceName, parsedCommand } of bindingResourceCommands) {
const bindingInfo = resourceBindings[resourceName];
if (!bindingInfo) {
throw new Error(`Resource ${resourceName} is not defined in the bindings.`);
}

if (parsedCommand.type === "ZEROS") {
const elementSize = parsedCommand.elementSize;
const bindingInfo = resourceBindings[resourceName];
if (!bindingInfo) {
throw new Error(`Resource ${resourceName} is not defined in the bindings.`);
}

if (!bindingInfo.buffer) {
throw new Error(`Resource ${resourceName} is an invalid type for ZEROS`);
Expand Down Expand Up @@ -638,11 +661,6 @@ async function processResourceCommands(
safeSet(allocatedResources, resourceName, sampler);
} else if (parsedCommand.type === "BLACK") {
const size = parsedCommand.width * parsedCommand.height;
const bindingInfo = resourceBindings[resourceName];
if (!bindingInfo) {
throw new Error(`Resource ${resourceName} is not defined in the bindings.`);
}

const format = bindingInfo.storageTexture?.format;
if (format == undefined) {
throw new Error(`Could not find format of ${resourceName}`);
Expand Down Expand Up @@ -677,12 +695,6 @@ async function processResourceCommands(
const height = parsedCommand.height_scale * currentWindowSize[1];
const size = width * height;

const bindingInfo = resourceBindings[resourceName];
if (!bindingInfo) {
throw new Error(`Resource ${resourceName} is not defined in the bindings.`);
}


const format = bindingInfo.storageTexture?.format;
if (format == undefined) {
throw new Error(`Could not find format of ${resourceName}`)
Expand Down Expand Up @@ -715,17 +727,10 @@ async function processResourceCommands(
}
} else if (parsedCommand.type === "URL") {
// Load image from URL and wait for it to be ready.
const bindingInfo = resourceBindings[resourceName];

if (!bindingInfo) {
throw new Error(`Resource ${resourceName} is not defined in the bindings.`);
}

if (!bindingInfo.texture) {
throw new Error(`Resource ${resourceName} is not a texture.`);
}


const format = parsedCommand.format;

const image = new Image();
Expand Down Expand Up @@ -759,11 +764,6 @@ async function processResourceCommands(
throw new Error(`Failed to create texture from image: ${error}`);
}
} else if (parsedCommand.type === "DATA") {
const bindingInfo = resourceBindings[resourceName];
if (!bindingInfo) {
throw new Error(`Resource ${resourceName} is not defined in the bindings.`);
}

if (!bindingInfo.buffer) {
throw new Error(`Resource ${resourceName} is not defined as a buffer.`);
}
Expand Down Expand Up @@ -805,10 +805,6 @@ async function processResourceCommands(
}
} else if (parsedCommand.type === "RAND") {
const elementSize = 4; // RAND is only valid for floats
const bindingInfo = resourceBindings[resourceName];
if (!bindingInfo) {
throw new Error(`Resource ${resourceName} is not defined in the bindings.`);
}

if (!bindingInfo.buffer) {
throw new Error(`Resource ${resourceName} is not defined as a buffer.`);
Expand All @@ -830,58 +826,10 @@ async function processResourceCommands(
device.queue.writeBuffer(buffer, 0, data);

safeSet(allocatedResources, resourceName, buffer);
} else if (parsedCommand.type == "SLIDER") {
const elementSize = parsedCommand.elementSize;

const buffer = allocatedResources.get("uniformInput") as GPUBuffer

// Initialize the buffer with the default.
let bufferDefault: BufferSource
if (elementSize == 4) {
bufferDefault = new Float32Array([parsedCommand.default]);
} else
throw new Error("Unsupported float size for slider")
device.queue.writeBuffer(buffer, parsedCommand.offset, bufferDefault);
} else if (parsedCommand.type == "COLOR_PICK") {
const elementSize = parsedCommand.elementSize;

const buffer = allocatedResources.get("uniformInput") as GPUBuffer

// Initialize the buffer with the default.
let bufferDefault: BufferSource
if (elementSize == 4) {
bufferDefault = new Float32Array(parsedCommand.default);
} else
throw new Error("Unsupported float size for color pick")
device.queue.writeBuffer(buffer, parsedCommand.offset, bufferDefault);
} else if (parsedCommand.type == "TIME") {
const buffer = allocatedResources.get("uniformInput") as GPUBuffer

// Initialize the buffer with zeros.
let bufferDefault: BufferSource = new Float32Array([0.0]);
device.queue.writeBuffer(buffer, parsedCommand.offset, bufferDefault);
} else if (parsedCommand.type == "FRAME_ID") {
const buffer = allocatedResources.get("uniformInput") as GPUBuffer

// Initialize the buffer with zeros.
let bufferDefault: BufferSource = new Float32Array([0.0]);
device.queue.writeBuffer(buffer, parsedCommand.offset, bufferDefault);
} else if (parsedCommand.type == "MOUSE_POSITION") {
const buffer = allocatedResources.get("uniformInput") as GPUBuffer

// Initialize the buffer with zeros.
let bufferDefault: BufferSource = new Float32Array([0, 0, 0, 0]);
device.queue.writeBuffer(buffer, parsedCommand.offset, bufferDefault);
} else if (parsedCommand.type == "KEY") {
const buffer = allocatedResources.get("uniformInput") as GPUBuffer

// Initialize the buffer with zeros.
let bufferDefault: BufferSource = new Float32Array([0]);
device.queue.writeBuffer(buffer, parsedCommand.offset, bufferDefault);
} else {
// exhaustiveness check
let x: never = parsedCommand;
throw new Error("Invalid resource command type");
throw new Error("Invalid binding command type");
}
}

Expand Down Expand Up @@ -987,6 +935,13 @@ function onRun(runCompiledCode: CompiledPlayground) {
compiledCode.uniformSize
);

writeUniformData(
allocatedResources.get("uniformInput") as GPUBuffer,
compiledCode.uniformComponents,
compiledCode.uniformSize,
0.0,
)

if (!passThroughPipeline) {
passThroughPipeline = new GraphicsPipeline(device);
const shaderModule = device.createShaderModule({ code: passThroughshaderCode });
Expand Down
7 changes: 6 additions & 1 deletion engine/shared/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { UniformController } from './playgroundInterface';
import { ScalarType, UniformController } from './playgroundInterface';

export * from './playgroundInterface';

export function isControllerRendered(controller: UniformController) {
return controller.type == "SLIDER" || controller.type == "COLOR_PICK";
}

export function getScalarSize(scalarType: ScalarType): 8 | 16 | 32 | 64 {
let size = parseInt(scalarType.replace(/^[a-z]*/, ""));
return size as any;
}
29 changes: 3 additions & 26 deletions engine/shared/src/playgroundInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,13 @@ export type UniformController = { buffer_offset: number } & ({
value: [number, number, number],
} | {
type: "TIME",
scalarType: ScalarType,
} | {
type: "FRAME_ID",
scalarType: ScalarType,
} | {
type: "MOUSE_POSITION",
scalarType: ScalarType,
} | {
type: "KEY",
key: string,
Expand Down Expand Up @@ -166,32 +169,6 @@ export type ParsedCommand = {
"type": "DATA",
"url": string,
"elementSize": number,
} | {
"type": "SLIDER",
"default": number,
"min": number,
"max": number,
"elementSize": number,
"offset": number,
} | {
"type": "COLOR_PICK",
"default": [number, number, number],
"elementSize": number,
"offset": number,
} | {
"type": "TIME",
"offset": number,
} | {
"type": "FRAME_ID",
"offset": number,
} | {
"type": "MOUSE_POSITION",
"offset": number,
} | {
"type": "KEY",
key: string,
offset: number,
scalarType: ScalarType,
} | {
"type": "SAMPLER"
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,15 @@ Initialize a `float` buffer with uniform random floats between 0 and 1.

### `[playground::TIME]`

Gives a `float` uniform the current time in milliseconds.
Gives a scalar uniform the current time in milliseconds.

### `[playground::FRAME_ID]`

Gives a `float` uniform the current frame index (starting from 0).
Gives a scalar uniform the current frame index (starting from 0).

### `[playground::MOUSE_POSITION]`

Gives a `float4` uniform mouse data.
Gives a 4 component vector uniform mouse data.

* `xy`: mouse position (in pixels) during last button down
* `abs(zw)`: mouse position during last button click
Expand Down
Loading