Skip to content

Commit 1b97fa8

Browse files
authored
Remove imageMain and printMain in favor of render and print imports (#168)
* Remove `imageMain` and `printMain` in favor of render and print imports Fixes #167 * Fix compilation of non playground shaders * Cleanup imports, better handle lack of webgpu support * Allow displaying both image and print output at the same time * Update documentation * Fix compilation target and entrypoint selection
1 parent a082ab2 commit 1b97fa8

32 files changed

+648
-738
lines changed

engine/playground-render-canvas/src/RenderCanvas.vue

Lines changed: 54 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
<script setup lang="ts">
22
import { ComputePipeline } from './compute';
33
import { GraphicsPipeline, passThroughshaderCode } from './pass_through';
4-
import { NotReadyError, parsePrintfBuffer, sizeFromFormat, isWebGPUSupported } from './canvasUtils';
5-
import type { Bindings, CallCommand, CompiledPlayground, PlaygroundMessage, ResourceCommand, RunnableShaderType, ShaderType } from 'slang-playground-shared';
6-
import { onMounted, ref, useTemplateRef, type Ref, inject } from 'vue';
4+
import { NotReadyError, parsePrintfBuffer, sizeFromFormat } from './canvasUtils';
5+
import type { Bindings, CallCommand, CompiledPlayground, ResourceCommand } from 'slang-playground-shared';
6+
import { onMounted, ref, useTemplateRef } from 'vue';
77
88
let fileUri: string;
99
let context: GPUCanvasContext;
10-
let shaderType: RunnableShaderType;
1110
let computePipelines: ComputePipeline[] = [];
1211
let passThroughPipeline: GraphicsPipeline;
1312
@@ -19,8 +18,8 @@ let abortRender = false;
1918
const pauseRender = ref(false);
2019
let onRenderAborted: (() => void) | null = null;
2120
21+
// This should probably be generated via reflection
2222
const printfBufferElementSize = 12;
23-
const printfBufferSize = printfBufferElementSize * 2048; // 12 bytes per printf struct
2423
2524
let currentWindowSize = [300, 150];
2625
@@ -98,7 +97,7 @@ function setFrame(targetFrame: number) {
9897
pauseRender.value = true;
9998
// Set internal counter so execFrame's increment brings us to t
10099
frameID.value = t - 1;
101-
execFrame(performance.now(), shaderType, compiledCode, t === 0)
100+
execFrame(performance.now(), compiledCode, t === 0)
102101
.catch(err => {
103102
if (err instanceof Error) emit('logError', `Error rendering frame ${t}: ${err.message}`);
104103
else emit('logError', `Error rendering frame ${t}: ${err}`);
@@ -137,7 +136,7 @@ function resizeCanvas(entries: ResizeObserverEntry[]) {
137136
return false;
138137
}
139138
140-
function withRenderLock(setupFn: { (): Promise<void>; }, renderFn: { (timeMS: number, currentDisplayMode: ShaderType): Promise<boolean>; }) {
139+
function withRenderLock(setupFn: { (): Promise<void>; }, renderFn: { (timeMS: number): Promise<boolean>; }) {
141140
// Overwrite the onRenderAborted function to the new one.
142141
// This also makes sure that a single function is called when the render thread is aborted.
143142
//
@@ -158,7 +157,7 @@ function withRenderLock(setupFn: { (): Promise<void>; }, renderFn: { (timeMS: nu
158157
const newRenderLoop = async (timeMS: number) => {
159158
let nextFrame = false;
160159
try {
161-
const keepRendering = await renderFn(timeMS, shaderType);
160+
const keepRendering = await renderFn(timeMS);
162161
nextFrame = keepRendering && !abortRender;
163162
if (nextFrame)
164163
requestAnimationFrame(newRenderLoop);
@@ -220,7 +219,7 @@ function handleResize() {
220219
const height = parsedCommand.height_scale * currentWindowSize[1];
221220
const size = width * height;
222221
223-
const bindingInfo = compiledCode.shader.layout[resourceName];
222+
const bindingInfo = compiledCode.layout[resourceName];
224223
if (!bindingInfo) {
225224
throw new Error(`Resource ${resourceName} is not defined in the bindings.`);
226225
}
@@ -338,9 +337,7 @@ function resetMouse() {
338337
let timeAggregate = 0;
339338
let frameCount = 0;
340339
341-
async function execFrame(timeMS: number, currentDisplayMode: ShaderType, playgroundData: CompiledPlayground, firstFrame: boolean) {
342-
if (currentDisplayMode == null)
343-
return false;
340+
async function execFrame(timeMS: number, playgroundData: CompiledPlayground, firstFrame: boolean) {
344341
if (currentWindowSize[0] < 2 || currentWindowSize[1] < 2)
345342
return false;
346343
@@ -403,15 +400,12 @@ async function execFrame(timeMS: number, currentDisplayMode: ShaderType, playgro
403400
// Encode commands to do the computation
404401
const encoder = device.createCommandEncoder({ label: 'compute builtin encoder' });
405402
406-
let printfBufferRead = allocatedResources.get("printfBufferRead");
407-
if (!(printfBufferRead instanceof GPUBuffer)) {
408-
throw new Error("printfBufferRead is not a buffer");
409-
}
410-
let g_printedBuffer = allocatedResources.get("g_printedBuffer")
411-
if (!(g_printedBuffer instanceof GPUBuffer)) {
412-
throw new Error("g_printedBuffer is not a buffer");
413-
}
414-
if (currentDisplayMode == "printMain") {
403+
let g_printedBuffer = allocatedResources.get("g_printedBuffer");
404+
if (g_printedBuffer instanceof GPUBuffer) {
405+
let printfBufferRead = allocatedResources.get("printfBufferRead");
406+
if (!(printfBufferRead instanceof GPUBuffer)) {
407+
throw new Error("printfBufferRead is not a buffer");
408+
}
415409
encoder.clearBuffer(printfBufferRead);
416410
encoder.clearBuffer(g_printedBuffer);
417411
}
@@ -512,7 +506,9 @@ async function execFrame(timeMS: number, currentDisplayMode: ShaderType, playgro
512506
pauseRender.value = true;
513507
}
514508
515-
if (currentDisplayMode == "imageMain") {
509+
510+
let outputTexture = allocatedResources.get("outputTexture");
511+
if (outputTexture instanceof GPUTexture) {
516512
const renderPassDescriptor = passThroughPipeline.createRenderPassDesc(context.getCurrentTexture().createView());
517513
const renderPass = encoder.beginRenderPass(renderPassDescriptor);
518514
@@ -526,7 +522,11 @@ async function execFrame(timeMS: number, currentDisplayMode: ShaderType, playgro
526522
}
527523
528524
// copy output buffer back in print mode
529-
if (currentDisplayMode == "printMain") {
525+
if (g_printedBuffer instanceof GPUBuffer) {
526+
let printfBufferRead = allocatedResources.get("printfBufferRead");
527+
if (!(printfBufferRead instanceof GPUBuffer)) {
528+
throw new Error("printfBufferRead is not a buffer");
529+
}
530530
encoder.copyBufferToBuffer(
531531
g_printedBuffer, 0, printfBufferRead, 0, g_printedBuffer.size);
532532
}
@@ -537,7 +537,11 @@ async function execFrame(timeMS: number, currentDisplayMode: ShaderType, playgro
537537
538538
await device.queue.onSubmittedWorkDone();
539539
540-
if (currentDisplayMode == "printMain") {
540+
if (g_printedBuffer instanceof GPUBuffer) {
541+
let printfBufferRead = allocatedResources.get("printfBufferRead");
542+
if (!(printfBufferRead instanceof GPUBuffer)) {
543+
throw new Error("printfBufferRead is not a buffer");
544+
}
541545
await printfBufferRead.mapAsync(GPUMapMode.READ);
542546
543547
const formatPrint = parsePrintfBuffer(
@@ -603,9 +607,18 @@ async function processResourceCommands(
603607
throw new Error(`Resource ${resourceName} is an invalid type for ZEROS`);
604608
}
605609
610+
let usage = GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST;
611+
612+
if (resourceMetadata[resourceName]?.indirect)
613+
usage |= GPUBufferUsage.INDIRECT;
614+
615+
// Hack fix to get printing to work
616+
if (resourceName == "g_printedBuffer")
617+
usage |= GPUBufferUsage.COPY_SRC;
618+
606619
const buffer = device.createBuffer({
607620
size: parsedCommand.count * elementSize,
608-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | (resourceMetadata[resourceName]?.indirect ? GPUBufferUsage.INDIRECT : 0),
621+
usage,
609622
});
610623
611624
safeSet(allocatedResources, resourceName, buffer);
@@ -875,15 +888,13 @@ async function processResourceCommands(
875888
//
876889
// Some special-case allocations
877890
//
878-
safeSet(allocatedResources, "g_printedBuffer", device.createBuffer({
879-
size: printfBufferSize,
880-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
881-
}));
882-
883-
safeSet(allocatedResources, "printfBufferRead", device.createBuffer({
884-
size: printfBufferSize,
885-
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
886-
}));
891+
let g_printedBuffer = allocatedResources.get("g_printedBuffer");
892+
if (g_printedBuffer instanceof GPUBuffer) {
893+
safeSet(allocatedResources, "printfBufferRead", device.createBuffer({
894+
size: g_printedBuffer.size,
895+
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
896+
}));
897+
}
887898
888899
return allocatedResources;
889900
}
@@ -896,7 +907,7 @@ type ResourceMetadata = {
896907
function getResourceMetadata(compiledCode: CompiledPlayground): { [k: string]: ResourceMetadata } {
897908
const metadata = {};
898909
899-
for (const resourceName of Object.keys(compiledCode.shader.layout)) {
910+
for (const resourceName of Object.keys(compiledCode.layout)) {
900911
metadata[resourceName] = {
901912
indirect: false,
902913
excludeBinding: [],
@@ -924,7 +935,6 @@ function onRun(runCompiledCode: CompiledPlayground) {
924935
frameCount = 0;
925936
pauseRender.value = false;
926937
927-
shaderType = runCompiledCode.mainEntryPoint;
928938
fileUri = runCompiledCode.uri;
929939
930940
resetMouse();
@@ -953,11 +963,11 @@ function onRun(runCompiledCode: CompiledPlayground) {
953963
}
954964
955965
const pipelineBindings: Bindings = {};
956-
for (const param in compiledCode.shader.layout) {
966+
for (const param in compiledCode.layout) {
957967
if (resourceMetadata[param]?.excludeBinding.includes(entryPoint)) {
958968
continue;
959969
}
960-
pipelineBindings[param] = compiledCode.shader.layout[param];
970+
pipelineBindings[param] = compiledCode.layout[param];
961971
}
962972
963973
// create a pipeline resource 'signature' based on the bindings found in the program.
@@ -971,7 +981,7 @@ function onRun(runCompiledCode: CompiledPlayground) {
971981
}
972982
973983
allocatedResources = await processResourceCommands(
974-
compiledCode.shader.layout,
984+
compiledCode.layout,
975985
compiledCode.resourceCommands,
976986
resourceMetadata,
977987
compiledCode.uniformSize
@@ -980,19 +990,14 @@ function onRun(runCompiledCode: CompiledPlayground) {
980990
if (!passThroughPipeline) {
981991
passThroughPipeline = new GraphicsPipeline(device);
982992
const shaderModule = device.createShaderModule({ code: passThroughshaderCode });
983-
const inputTexture = allocatedResources.get("outputTexture");
984-
if (!(inputTexture instanceof GPUTexture)) {
985-
throw new Error("inputTexture is not a texture");
986-
}
987-
passThroughPipeline.createPipeline(shaderModule, inputTexture);
993+
passThroughPipeline.createPipeline(shaderModule);
988994
}
989995
990996
let outputTexture = allocatedResources.get("outputTexture");
991-
if (!(outputTexture instanceof GPUTexture)) {
992-
throw new Error("");
997+
if (outputTexture instanceof GPUTexture) {
998+
passThroughPipeline.inputTexture = outputTexture;
999+
passThroughPipeline.createBindGroup();
9931000
}
994-
passThroughPipeline.inputTexture = outputTexture;
995-
passThroughPipeline.createBindGroup();
9961001
9971002
// Create bind groups for the pipelines
9981003
for (const pipeline of computePipelines)
@@ -1002,11 +1007,7 @@ function onRun(runCompiledCode: CompiledPlayground) {
10021007
async (timeMS: number) => {
10031008
if (abortRender) return false;
10041009
if (pauseRender.value) return true;
1005-
1006-
if (shaderType === null) {
1007-
// handle this case
1008-
}
1009-
const keepRendering = await execFrame(timeMS, shaderType, compiledCode, firstFrame);
1010+
const keepRendering = await execFrame(timeMS, compiledCode, firstFrame);
10101011
firstFrame = false;
10111012
return keepRendering;
10121013
});

engine/playground-render-canvas/src/pass_through.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export class GraphicsPipeline {
7474
this.pipelineLayout = layout;
7575
}
7676

77-
createPipeline(shaderModule: GPUShaderModule, inputTexture: GPUTexture) {
77+
createPipeline(shaderModule: GPUShaderModule) {
7878
this.createGraphicsPipelineLayout();
7979

8080
if (this.pipelineLayout == undefined) throw new Error("Pipeline layout not available");
@@ -97,8 +97,6 @@ export class GraphicsPipeline {
9797
this.pipeline = pipeline;
9898

9999
this.sampler = this.device.createSampler();
100-
this.inputTexture = inputTexture;
101-
this.createBindGroup();
102100
}
103101

104102
createBindGroup() {

engine/shared/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { UniformController } from './playgroundInterface';
22

3-
export const RUNNABLE_ENTRY_POINT_NAMES = ['imageMain', 'printMain'] as const;
43
export * from './playgroundInterface';
54

65
export function isControllerRendered(controller: UniformController) {

engine/shared/src/playgroundInterface.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ export type CompileTarget = 'SPIRV' | 'HLSL' | 'GLSL' | 'METAL' | 'WGSL' | 'CUDA
44

55
export type CompileRequest = {
66
target: CompileTarget,
7-
entrypoint: string,
7+
entrypoint: string | null,
88
sourceCode: string,
99
shaderPath: string,
10-
noWebGPU: boolean,
1110
}
1211

1312
export type EntrypointsRequest = {
@@ -93,7 +92,6 @@ export type EntrypointsResult = string[]
9392

9493
export type Shader = {
9594
code: string,
96-
layout: Bindings,
9795
hashedStrings: HashedStringData,
9896
reflection: ReflectionJSON,
9997
threadGroupSizes: { [key: string]: [number, number, number] },
@@ -125,17 +123,16 @@ export type UniformController = { buffer_offset: number } & ({
125123
scalarType: ScalarType,
126124
})
127125

128-
export type RunnableShaderType = 'imageMain' | 'printMain';
129-
export type ShaderType = RunnableShaderType | null;
130-
126+
export type OutputType = "printing" | "image";
131127
export type CompiledPlayground = {
132128
uri: string,
133129
shader: Shader,
134-
mainEntryPoint: RunnableShaderType,
135130
resourceCommands: ResourceCommand[],
136131
callCommands: CallCommand[],
137132
uniformSize: number,
138133
uniformComponents: UniformController[],
134+
layout: Bindings,
135+
outputTypes: OutputType[],
139136
}
140137

141138
export type PlaygroundMessage = {

engine/slang-compilation-engine/media/playgroundDocumentation.md

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ No data is sent to any server.
55

66
## Compiling Shaders (the "Compile" button)
77

8-
You can compile shaders to many targets supported by Slang here, including SPIR-V, HLSL, GLSL, Metal, and
8+
You can compile shaders to many targets supported by Slang here, including SPIR-V, HLSL, GLSL, Metal, CUDA, and
99
WGSL.
1010
Generating DXIL requires DXC, which doesn't run in the browser so it's not supported here. You will have the opportunuty to select entrypoints for targets that don't support multiple entrypoints.
1111
You can compile shaders with either the button in the top right of the editor, or the command `Slang Compile`.
@@ -14,13 +14,36 @@ You can compile shaders with either the button in the top right of the editor, o
1414

1515
In addition to compiling shaders, this playground extension can also run simple shaders via WebGPU.
1616
You can run shaders with either the button in the top right of the editor, or the command `Run Playground`.
17-
The playground supports running two types of shaders:
17+
The playground supports running compute shaders that produce an image, text output, or both. All runnable shaders must be compute shaders marked with the `[shader("compute")]` attribute. You can use `import rendering` or `import printing` to display shader results.
1818

19-
* **Image Compute Shader**: This is a compute shader that returns a pixel value at a given image
20-
coordinate, similar to ShaderToys.
21-
An image compute shader must define a `float4 imageMain(uint2 dispatchThreadID, int2 screenSize)` function.
22-
* **Print Shader**: This is a shader that prints text to the output pane.
23-
A print shader must define a `void printMain()` function.
19+
### Rendering
20+
21+
The `rendering` module provides the following function:
22+
23+
`void drawPixel(uint2 location, IFunc<float4, int2> renderFunction)`
24+
25+
This will render to the output at the provided location based on the result of the provided function. The provided function takes in the screen size as an `int2`. It also creates an `outputTexture` texture, so you can create an entrypoint that executes for every pixel like so:
26+
27+
```slang
28+
import playground;
29+
import rendering;
30+
31+
[shader("compute")]
32+
[numthreads(16, 16, 1)]
33+
[playground::CALL::SIZE_OF("outputTexture")]
34+
void imageMain(uint2 dispatchThreadID: SV_DispatchThreadID)
35+
{
36+
drawPixel(dispatchThreadID, (int2 _) => float4(0.3, 0.7, 0.55, 1.0));
37+
}
38+
```
39+
40+
### Printing
41+
42+
The `printing` module provides the following function:
43+
44+
### `void printf<T>(String format, expand each T values) where T : IPrintf`
45+
46+
Prints the values formatted according to the format.
2447

2548
## Shader Commands
2649

@@ -54,6 +77,10 @@ Initialize a `float` buffer with uniform random floats between 0 and 1.
5477

5578
Gives a `float` uniform the current time in milliseconds.
5679

80+
### `[playground::FRAME_ID]`
81+
82+
Gives a `float` uniform the current frame index (starting from 0).
83+
5784
### `[playground::MOUSE_POSITION]`
5885

5986
Gives a `float4` uniform mouse data.
@@ -93,14 +120,6 @@ Dispatch a compute pass with an indirect command buffer and an offset in bytes.
93120

94121
Only dispatch the compute pass once at the start of rendering. Should be used in addition to another CALL command.
95122

96-
## Playground functions
97-
98-
The playground shader also provides the following functions:
99-
100-
### `void printf<T>(String format, expand each T values) where T : IPrintf`
101-
102-
Prints the values formatted according to the format. Only available in print shaders.
103-
104123
## Shader Reflection
105124

106125
You can see reflection data for your shaders with either the button in the top right of the editor, or the command `Show Reflection`.

0 commit comments

Comments
 (0)