diff --git a/compiler.ts b/compiler.ts index 4925ebd..29f5831 100644 --- a/compiler.ts +++ b/compiler.ts @@ -1,4 +1,4 @@ -import {SpirvTools, default as spirvTools} from "./spirv-tools.js"; +import { SpirvTools, default as spirvTools } from "./spirv-tools.js"; import { ModuleType } from './try-slang.js'; import type { ComponentType, EmbindString, GlobalSession, Module, ProgramLayout, Session, ThreadGroupSize, VariableLayoutReflection } from './slang-wasm.js'; import { playgroundSource } from "./playgroundShader.js"; @@ -10,7 +10,12 @@ export function isWholeProgramTarget(compileTarget: string) { return compileTarget == "METAL" || compileTarget == "SPIRV"; } -const imageMainSource = ` +export const RUNNABLE_ENTRY_POINT_NAMES = ['imageMain', 'printMain'] as const; +export type RunnableShaderType = typeof RUNNABLE_ENTRY_POINT_NAMES[number] +export type ShaderType = RunnableShaderType | null + +const RUNNABLE_ENTRY_POINT_SOURCE_MAP: { [key in RunnableShaderType]: string } = { + 'imageMain': ` import user; import playground; @@ -34,10 +39,8 @@ void imageMain(uint3 dispatchThreadID : SV_DispatchThreadID) outputTexture.Store(dispatchThreadID.xy, color); } -`; - - -const printMainSource = ` +`, + 'printMain': ` import user; import playground; @@ -52,7 +55,9 @@ void printMain(uint3 dispatchThreadID : SV_DispatchThreadID) { printMain(); } -`; +`, +} + type BindingDescriptor = { storageTexture: { access: "write-only" | "read-write", @@ -91,10 +96,6 @@ export class SlangCompiler { static SLANG_STAGE_FRAGMENT = 5; static SLANG_STAGE_COMPUTE = 6; - static RENDER_SHADER = 0; - static PRINT_SHADER = 1; - static NON_RUNNABLE_SHADER = 2; - globalSlangSession: GlobalSession | null = null; // slangSession = null; @@ -102,7 +103,7 @@ export class SlangCompiler { slangWasmModule; diagnosticsMsg; - shaderType; + shaderType: ShaderType; spirvToolsModule: SpirvTools | null = null; @@ -111,9 +112,10 @@ export class SlangCompiler { constructor(module: ModuleType) { this.slangWasmModule = module; this.diagnosticsMsg = ""; - this.shaderType = SlangCompiler.NON_RUNNABLE_SHADER; - this.mainModules.set('imageMain', { source: imageMainSource }); - this.mainModules.set('printMain', { source: printMainSource }); + this.shaderType = null; + for (let runnableEntryPoint of RUNNABLE_ENTRY_POINT_NAMES) { + this.mainModules.set(runnableEntryPoint, { source: RUNNABLE_ENTRY_POINT_SOURCE_MAP[runnableEntryPoint] }); + } FS.createDataFile("/", "user.slang", new DataView(new ArrayBuffer(0)), true, true, false); FS.createDataFile("/", "playground.slang", new DataView(new ArrayBuffer(0)), true, true, false); } @@ -149,15 +151,10 @@ export class SlangCompiler { // In our playground, we only allow to run shaders with two entry points: renderMain and printMain findRunnableEntryPoint(module: Module) { - const runnableEntryPointNames = ['imageMain', 'printMain']; - for (let i = 0; i < runnableEntryPointNames.length; i++) { - const entryPointName = runnableEntryPointNames[i]; + for (let entryPointName of RUNNABLE_ENTRY_POINT_NAMES) { let entryPoint = module.findAndCheckEntryPoint(entryPointName, SlangCompiler.SLANG_STAGE_COMPUTE); if (entryPoint) { - if (i == 0) - this.shaderType = SlangCompiler.RENDER_SHADER; - else - this.shaderType = SlangCompiler.PRINT_SHADER; + this.shaderType = entryPointName; return entryPoint; } } @@ -187,8 +184,7 @@ export class SlangCompiler { } async initSpirvTools() { - if (!this.spirvToolsModule) - { + if (!this.spirvToolsModule) { this.spirvToolsModule = await spirvTools(); } } @@ -218,11 +214,10 @@ export class SlangCompiler { // we will also add them to the dropdown list. findDefinedEntryPoints(shaderSource: string): string[] { let result: string[] = []; - if (shaderSource.match("imageMain")) { - result.push("imageMain"); - } - if (shaderSource.match("printMain")) { - result.push("printMain"); + for (let entryPointName of RUNNABLE_ENTRY_POINT_NAMES) { + if (shaderSource.match(entryPointName)) { + result.push(entryPointName); + } } let slangSession: Session | null | undefined; try { @@ -232,9 +227,13 @@ export class SlangCompiler { return []; } let module: Module | null = null; - + if (result.length > 0) { + slangSession.loadModuleFromSource(playgroundSource, "playground", "/playground.slang"); + } module = slangSession.loadModuleFromSource(shaderSource, "user", "/user.slang"); if (!module) { + const error = this.slangWasmModule.getLastError(); + console.error(error.type + " error: " + error.message); return result; } @@ -256,8 +255,8 @@ export class SlangCompiler { // If user entrypoint name imageMain or printMain, we will load the pre-built main modules because they // are defined in those modules. Otherwise, we will only need to load the user module and find the entry // point in the user module. - shouldLoadMainModule(entryPointName: string) { - return entryPointName == "imageMain" || entryPointName == "printMain"; + isRunnableEntryPoint(entryPointName: string): entryPointName is RunnableShaderType { + return RUNNABLE_ENTRY_POINT_NAMES.includes(entryPointName as any); } // Since we will not let user to change the entry point code, we can precompile the entry point module @@ -265,7 +264,7 @@ export class SlangCompiler { compileEntryPointModule(slangSession: Session, moduleName: string) { let source = this.mainModules.get(moduleName)?.source; - if(source == undefined) { + if (source == undefined) { throw new Error(`Could not get module ${moduleName}`) } let module: Module | null = slangSession.loadModuleFromSource(source, moduleName, '/' + moduleName + '.slang'); @@ -287,12 +286,12 @@ export class SlangCompiler { } getPrecompiledProgram(slangSession: Session, moduleName: string) { - if (moduleName != "printMain" && moduleName != "imageMain") + if (!this.isRunnableEntryPoint(moduleName)) return null; let mainModule = this.compileEntryPointModule(slangSession, moduleName); - this.shaderType = SlangCompiler.RENDER_SHADER; + this.shaderType = moduleName; return mainModule; } @@ -306,8 +305,8 @@ export class SlangCompiler { const count = userModule.getDefinedEntryPointCount(); for (let i = 0; i < count; i++) { const name = userModule.getDefinedEntryPoint(i).getName(); - if (name == "imageMain" || name == "printMain") { - this.diagnosticsMsg += ("error: Entry point name 'imageMain' or 'printMain' is reserved"); + if (this.isRunnableEntryPoint(name)) { + this.diagnosticsMsg += `error: Entry point name ${name} is reserved`; return false; } } @@ -315,14 +314,13 @@ export class SlangCompiler { // If entry point is provided, we know for sure this is not a whole program compilation, // so we will just go to find the correct module to include in the compilation. if (entryPointName != "") { - if (this.shouldLoadMainModule(entryPointName)) { + if (this.isRunnableEntryPoint(entryPointName)) { // we use the same entry point name as module name const mainProgram = this.getPrecompiledProgram(slangSession, entryPointName); if (!mainProgram) return false; - this.shaderType = entryPointName == "imageMain" ? - SlangCompiler.RENDER_SHADER : SlangCompiler.PRINT_SHADER; + this.shaderType = entryPointName; componentList.push(mainProgram.module); componentList.push(mainProgram.entryPoint); @@ -341,7 +339,7 @@ export class SlangCompiler { else { const results = this.findDefinedEntryPoints(shaderSource); for (let i = 0; i < results.length; i++) { - if (results[i] == "imageMain" || results[i] == "printMain") { + if (this.isRunnableEntryPoint(results[i])) { const mainProgram = this.getPrecompiledProgram(slangSession, results[i]); if (!mainProgram) return false; @@ -361,10 +359,10 @@ export class SlangCompiler { return true; } - getBindingDescriptor(index: number, programReflection: ProgramLayout, parameter: VariableLayoutReflection): BindingDescriptor|null { + getBindingDescriptor(index: number, programReflection: ProgramLayout, parameter: VariableLayoutReflection): BindingDescriptor | null { const globalLayout = programReflection.getGlobalParamsTypeLayout(); - if(globalLayout == null) { + if (globalLayout == null) { throw new Error("Could not get layout") } @@ -396,7 +394,7 @@ export class SlangCompiler { getResourceBindings(linkedProgram: ComponentType): Bindings { const reflection: ProgramLayout | null = linkedProgram.getLayout(0); // assume target-index = 0 - if(reflection == null) { + if (reflection == null) { throw new Error("Could not get reflection!") } @@ -405,7 +403,7 @@ export class SlangCompiler { let resourceDescriptors = new Map(); for (let i = 0; i < count; i++) { const parameter = reflection.getParameterByIndex(i); - if(parameter == null) { + if (parameter == null) { throw new Error("Invalid state!") } const name = parameter.getName(); @@ -438,10 +436,10 @@ export class SlangCompiler { return true; } - compile(shaderSource: string, entryPointName: string, compileTargetStr: string): null | [string, Bindings, any, ReflectionJSON, ThreadGroupSize | {x: number, y: number, z: number}] { + compile(shaderSource: string, entryPointName: string, compileTargetStr: string): null | [string, Bindings, any, ReflectionJSON, ThreadGroupSize | { x: number, y: number, z: number }] { this.diagnosticsMsg = ""; - let shouldLinkPlaygroundModule = (shaderSource.match(/printMain|imageMain/) != null); + let shouldLinkPlaygroundModule = RUNNABLE_ENTRY_POINT_NAMES.some((entry_point) => shaderSource.match(entry_point) != null); const compileTarget = this.findCompileTarget(compileTargetStr); let isWholeProgram = isWholeProgramTarget(compileTargetStr); @@ -452,7 +450,7 @@ export class SlangCompiler { } try { - if(this.globalSlangSession == null) { + if (this.globalSlangSession == null) { throw new Error("Slang session not available. Maybe the compiler hasn't been initialized yet?") } let slangSession = this.globalSlangSession.createSession(compileTarget); @@ -476,7 +474,7 @@ export class SlangCompiler { if (this.addActiveEntryPoints(slangSession, shaderSource, entryPointName, isWholeProgram, components[userModuleIndex], components) == false) return null; let program: ComponentType = slangSession.createCompositeComponentType(components); - let linkedProgram:ComponentType = program.link(); + let linkedProgram: ComponentType = program.link(); let hashedStrings = linkedProgram.loadStrings(); let outCode: string; @@ -519,7 +517,7 @@ export class SlangCompiler { } catch (e) { console.error(e); // typescript is missing the type for WebAssembly.Exception - if(typeof e === 'object' && e !== null && e.constructor.name === 'Exception') { + if (typeof e === 'object' && e !== null && e.constructor.name === 'Exception') { this.diagnosticsMsg += "Slang internal error occurred.\n"; } else if (e instanceof Error) { this.diagnosticsMsg += e.message; diff --git a/image_demo.ts b/image_demo.ts deleted file mode 100644 index 9c30b23..0000000 --- a/image_demo.ts +++ /dev/null @@ -1,33 +0,0 @@ -const imageDemoCode = -` -import playground; - -[playground::URL("static/jeep.jpg")] -Texture2D myImage; -float4 imageMain(uint2 dispatchThreadID, int2 screenSize) -{ - float2 size = float2(screenSize.x, screenSize.y); - float2 center = size / 2.0; - - float2 pos = float2(dispatchThreadID.xy); - - float stripSize = screenSize.x / 40; - - float dist = distance(pos, center) + getTime() * 3; - float strip = dist / stripSize % 2.0; - - uint imageW; - uint imageH; - myImage.GetDimensions(imageW, imageH); - - uint2 scaled = (uint2)floor(float2(dispatchThreadID.xy) / float2(screenSize) * float2(imageW, imageH)); - uint2 flipped = uint2(scaled.x, imageH - scaled.y); - - float4 imageColor = myImage[flipped] * 0.8; - - if (strip < 1.0f) - return imageColor; - else - return float4(0.8f - imageColor.x, 0.8f - imageColor.y, 0.8f - imageColor.z, 1.0f); -} -`; \ No newline at end of file diff --git a/test.cpp b/test.cpp deleted file mode 100644 index e16b652..0000000 --- a/test.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include -#include -#include -#include -#include -#include "slang-wasm.h" - - -void test() -{ - slang::wgsl::GlobalSession* globalSession = slang::wgsl::createGlobalSession(); - slang::wgsl::Session* session = globalSession->createSession(); - - std::string source1 = R"( - - RWStructuredBuffer outputBuffer; - - [shader("compute")] - void computeMain(int3 dispatchThreadID : SV_DispatchThreadID) - { - int idx = dispatchThreadID.x * 32 + dispatchThreadID.y; - outputBuffer[idx] = idx; - } - )"; - - - std::string source2 = R"( - - [shader("vertex")] - float4 vertexMain(float3 position) - { - float4 output = float4(position, 1.0); - return output; - } - - [shader("fragment")] - float4 fragMain(): SV_TARGET - { - return float4(1, 0, 0, 1); - } - )"; - - slang::wgsl::Module* module1 = session->loadModuleFromSource(source1.c_str(), "user", "/user.slang"); - if (module1 == nullptr) - { - std::cout << "Failed to load module1" << std::endl; - return; - } - - slang::wgsl::EntryPoint* entryPoint1 = module1->findEntryPointByName("computeMain"); - if (entryPoint1 == nullptr) - { - std::cout << "Failed to find entry point in module 1" << std::endl; - return; - } - - - slang::wgsl::Module* module2 = session->loadModuleFromSource(source2.c_str(), "user", "/user.slang"); - if (module2 == nullptr) - { - std::cout << "Failed to load module1" << std::endl; - return; - } - - slang::wgsl::EntryPoint* entryPoint2 = module2->findEntryPointByName("vertexMain"); - if (entryPoint2 == nullptr) - { - std::cout << "Failed to find entry point in module 1" << std::endl; - return; - } -} - -int main() -{ - test(); - return 0; -} diff --git a/try-slang.ts b/try-slang.ts index e10d184..5517e65 100644 --- a/try-slang.ts +++ b/try-slang.ts @@ -1,7 +1,7 @@ /// import { ComputePipeline } from './compute.js'; import { GraphicsPipeline, passThroughshaderCode } from './pass_through.js'; -import { SlangCompiler, Bindings, isWholeProgramTarget, ReflectionJSON } from './compiler.js'; +import { SlangCompiler, Bindings, isWholeProgramTarget, ReflectionJSON, ShaderType, RUNNABLE_ENTRY_POINT_NAMES } from './compiler.js'; import { initMonaco, userCodeURI, codeEditorChangeContent, initLanguageServer } from './language-server.js'; import { restoreSelectedTargetFromURL, restoreDemoSelectionFromURL, loadDemo, canvasCurrentMousePos, canvasLastMouseDownPos, canvasIsMouseDown, canvasMouseClicked, resetMouse, renderOutput, canvas, entryPointSelect, targetSelect } from './ui.js'; import { fetchWithProgress, configContext, parseCallCommands, createOutputTexture, parsePrintfBuffer, CallCommand, getCommandsFromAttributes } from './util.js'; @@ -29,45 +29,39 @@ class NotReadyError extends Error { } } -export var compiler: SlangCompiler | null = null; -export var slangd: LanguageServer | null = null; -var device: GPUDevice; -var context: GPUCanvasContext; -var randFloatPipeline: ComputePipeline; -var computePipeline: ComputePipeline; -var extraComputePipelines: ComputePipeline[] = []; -var passThroughPipeline: GraphicsPipeline; - -export var monacoEditor: monaco.editor.IStandaloneCodeEditor; -var diagnosticsArea: monaco.editor.IStandaloneCodeEditor; -var codeGenArea: monaco.editor.IStandaloneCodeEditor; - -var resourceBindings: Bindings; -var resourceCommands: { resourceName: string; parsedCommand: ParsedCommand; }[]; -var callCommands: CallCommand[]; -var allocatedResources: Map; -var hashedStrings: any; - -var renderThread: Promise | null = null; -var abortRender = false; -var onRenderAborted: (() => void) | null = null; +export let compiler: SlangCompiler | null = null; +export let slangd: LanguageServer | null = null; +let device: GPUDevice; +let context: GPUCanvasContext; +let randFloatPipeline: ComputePipeline; +let computePipeline: ComputePipeline; +let extraComputePipelines: ComputePipeline[] = []; +let passThroughPipeline: GraphicsPipeline; + +export let monacoEditor: monaco.editor.IStandaloneCodeEditor; +let diagnosticsArea: monaco.editor.IStandaloneCodeEditor; +let codeGenArea: monaco.editor.IStandaloneCodeEditor; + +let resourceBindings: Bindings; +let resourceCommands: { resourceName: string; parsedCommand: ParsedCommand; }[]; +let callCommands: CallCommand[]; +let allocatedResources: Map; +let randFloatResources: Map; +let hashedStrings: any; + +let renderThread: Promise | null = null; +let abortRender = false; +let onRenderAborted: (() => void) | null = null; const printfBufferElementSize = 12; const printfBufferSize = printfBufferElementSize * 2048; // 12 bytes per printf struct -var sourceCodeChange = true; +let currentWindowSize = [300, 150]; -var currentWindowSize = [300, 150]; - -const RENDER_MODE = SlangCompiler.RENDER_SHADER; -const PRINT_MODE = SlangCompiler.PRINT_SHADER; -const HIDDEN_MODE = SlangCompiler.NON_RUNNABLE_SHADER; const defaultShaderURL = "circle.slang"; -var currentMode = RENDER_MODE; +let currentEntryPoint: ShaderType = "imageMain"; -var randFloatPipeline: ComputePipeline; -var randFloatResources: Map; export function setEditorValue(editor: monaco.editor.IStandaloneCodeEditor, value: string, revealEnd: boolean = false) { editor.setValue(value); @@ -119,7 +113,7 @@ function resizeCanvas(entries: ResizeObserverEntry[]) { let width = canvas.clientWidth; let height = canvas.clientHeight; if (canvas.style.display == "none") { - var parentDiv = document.getElementById("output"); + let parentDiv = document.getElementById("output"); width = parentDiv?.clientWidth || width; height = parentDiv?.clientHeight || height; } @@ -167,7 +161,7 @@ function withRenderLock(setupFn: { (): Promise; }, renderFn: { (timeMS: nu // Set up render loop function const newRenderLoop = async (timeMS: number) => { - var nextFrame = false; + let nextFrame = false; try { const keepRendering = await renderFn(timeMS); nextFrame = keepRendering && !abortRender; @@ -244,14 +238,14 @@ function startRendering() { // We use the timer in the resize handler debounce the resize event, otherwise we could end of rendering // multiple useless frames. function resizeCanvasHandler(entries: ResizeObserverEntry[]) { - var needResize = resizeCanvas(entries); + let needResize = resizeCanvas(entries); if (needResize) { startRendering(); } } -function toggleDisplayMode(displayMode: number) { - if (currentMode == displayMode) +function toggleDisplayMode(displayMode: ShaderType) { + if (currentEntryPoint == displayMode) return; let resultSplitContainer = document.getElementById("resultSplitContainer") if (resultSplitContainer == null) { @@ -261,45 +255,44 @@ function toggleDisplayMode(displayMode: number) { if (printResult == null) { throw new Error("Cannot get printResult element") } - if (currentMode == HIDDEN_MODE && displayMode != HIDDEN_MODE) { + if (currentEntryPoint == null && displayMode != null) { resultSplitContainer.style.gridTemplateRows = "50% 14px 1fr"; } - if (displayMode == RENDER_MODE) { + currentEntryPoint = displayMode; + if (displayMode == "imageMain") { printResult.style.display = "none"; renderOutput.style.display = "block"; canvas.style.width = "100%"; canvas.style.height = "100%"; - currentMode = RENDER_MODE; } - else if (displayMode == PRINT_MODE) { + else if (displayMode == "printMain") { renderOutput.style.display = "none"; printResult.style.display = "grid"; - - currentMode = PRINT_MODE; } - else if (displayMode == HIDDEN_MODE) { + else if (displayMode == null) { renderOutput.style.display = "none"; printResult.style.display = "none"; resultSplitContainer.style.gridTemplateRows = "0px 14px 1fr"; - currentMode = HIDDEN_MODE; } else { + // exhaustiveness check + let x: never = displayMode; console.log("Invalid display mode " + displayMode); } } -var timeAggregate = 0; -var frameCount = 0; +let timeAggregate = 0; +let frameCount = 0; async function execFrame(timeMS: number) { - if (currentMode == HIDDEN_MODE) + if (currentEntryPoint == null) return false; if (currentWindowSize[0] < 2 || currentWindowSize[1] < 2) return false; const startTime = performance.now(); - var timeArray = new Float32Array(8); + let timeArray = new Float32Array(8); timeArray[0] = canvasCurrentMousePos.x; timeArray[1] = canvasCurrentMousePos.y; timeArray[2] = canvasLastMouseDownPos.x; @@ -318,6 +311,14 @@ async function execFrame(timeMS: number) { // Encode commands to do the computation const encoder = device.createCommandEncoder({ label: 'compute builtin encoder' }); + let printfBufferRead = allocatedResources.get("printfBufferRead"); + if (!(printfBufferRead instanceof GPUBuffer)) { + throw new Error("printfBufferRead is not a buffer") + } + if (currentEntryPoint == "printMain") { + encoder.clearBuffer(printfBufferRead); + } + // The extra passes always go first. // zip the extraComputePipelines and callCommands together for (const [pipeline, command] of callCommands.map((x: CallCommand, i: number) => [extraComputePipelines[i], x] as const)) { @@ -387,13 +388,14 @@ async function execFrame(timeMS: number) { if (computePipeline.pipeline == undefined) throw new Error("Compute pipeline is not defined."); pass.setPipeline(computePipeline.pipeline); - const workGroupSizeX = (currentWindowSize[0] + 15) / 16; - const workGroupSizeY = (currentWindowSize[1] + 15) / 16; - pass.dispatchWorkgroups(workGroupSizeX, workGroupSizeY); - pass.end(); - if (currentMode == RENDER_MODE) { - var renderPassDescriptor = passThroughPipeline.createRenderPassDesc(context.getCurrentTexture().createView()); + if (currentEntryPoint == "imageMain") { + const workGroupSizeX = (currentWindowSize[0] + 15) / 16; + const workGroupSizeY = (currentWindowSize[1] + 15) / 16; + pass.dispatchWorkgroups(workGroupSizeX, workGroupSizeY); + pass.end(); + + const renderPassDescriptor = passThroughPipeline.createRenderPassDesc(context.getCurrentTexture().createView()); const renderPass = encoder.beginRenderPass(renderPassDescriptor); renderPass.setBindGroup(0, passThroughPipeline.bindGroup || null); @@ -406,7 +408,10 @@ async function execFrame(timeMS: number) { } // copy output buffer back in print mode - if (currentMode == PRINT_MODE) { + if (currentEntryPoint == "printMain") { + pass.dispatchWorkgroups(1, 1); + pass.end(); + let outputBuffer = allocatedResources.get("outputBuffer"); if (!(outputBuffer instanceof GPUBuffer)) { throw new Error("outputBuffer is incorrect type or doesn't exist") @@ -415,12 +420,18 @@ async function execFrame(timeMS: number) { if (!(outputBufferRead instanceof GPUBuffer)) { throw new Error("outputBufferRead is incorrect type or doesn't exist") } + let g_printedBuffer = allocatedResources.get("g_printedBuffer") + if (!(g_printedBuffer instanceof GPUBuffer)) { + throw new Error("g_printedBuffer is not a buffer") + } encoder.copyBufferToBuffer( outputBuffer, 0, outputBufferRead, 0, outputBuffer.size); + encoder.copyBufferToBuffer( + g_printedBuffer, 0, printfBufferRead, 0, g_printedBuffer.size); } // Finish encoding and submit the commands @@ -429,13 +440,33 @@ async function execFrame(timeMS: number) { await device.queue.onSubmittedWorkDone(); + if (currentEntryPoint == "printMain") { + await printfBufferRead.mapAsync(GPUMapMode.READ); + + let textResult = ""; + const formatPrint = parsePrintfBuffer( + hashedStrings, + printfBufferRead, + printfBufferElementSize); + + if (formatPrint.length != 0) + textResult += "Shader Output:\n" + formatPrint.join("") + "\n"; + + printfBufferRead.unmap(); + let printResult = document.getElementById("printResult") + if (!(printResult instanceof HTMLTextAreaElement)) { + throw new Error("printResult invalid type") + } + printResult.value = textResult; + } + const timeElapsed = performance.now() - startTime; // Update performance info. timeAggregate += timeElapsed; frameCount++; if (frameCount == 20) { - var avgTime = (timeAggregate / frameCount); + let avgTime = (timeAggregate / frameCount); let performanceInfo = document.getElementById("performanceInfo"); if (!(performanceInfo instanceof HTMLDivElement)) { throw new Error("performanceInfo has an invalid type") @@ -446,89 +477,22 @@ async function execFrame(timeMS: number) { } // Only request the next frame if we are in the render mode - if (currentMode == RENDER_MODE) + if (currentEntryPoint == "imageMain") return true; else return false; } -async function printResult() { - // Encode commands to do the computation - const encoder = device.createCommandEncoder({ label: 'compute builtin encoder' }); - let printfBufferRead = allocatedResources.get("printfBufferRead"); - if (!(printfBufferRead instanceof GPUBuffer)) { - throw new Error("printfBufferRead is not a buffer") - } - encoder.clearBuffer(printfBufferRead); - - const pass = encoder.beginComputePass({ label: 'compute builtin pass' }); - - pass.setBindGroup(0, computePipeline.bindGroup || null); - if (computePipeline.pipeline == undefined) { - throw new Error("Compute pipeline is undefined") - } - pass.setPipeline(computePipeline.pipeline); - pass.dispatchWorkgroups(1, 1); - pass.end(); - - // copy output buffer back in print mode - let outputBuffer = allocatedResources.get("outputBuffer") - if (!(outputBuffer instanceof GPUBuffer)) { - throw new Error("outputBuffer is not a buffer") - } - let outputBufferRead = allocatedResources.get("outputBufferRead") - if (!(outputBufferRead instanceof GPUBuffer)) { - throw new Error("outputBufferRead is not a buffer") - } - let g_printedBuffer = allocatedResources.get("g_printedBuffer") - if (!(g_printedBuffer instanceof GPUBuffer)) { - throw new Error("g_printedBuffer is not a buffer") - } - encoder.copyBufferToBuffer( - outputBuffer, 0, outputBufferRead, 0, outputBuffer.size); - encoder.copyBufferToBuffer( - g_printedBuffer, 0, printfBufferRead, 0, g_printedBuffer.size); - - // Finish encoding and submit the commands - const commandBuffer = encoder.finish(); - device.queue.submit([commandBuffer]); - - await device.queue.onSubmittedWorkDone(); - - // Read the results once the job is done - await printfBufferRead.mapAsync(GPUMapMode.READ); - - let textResult = ""; - const formatPrint = parsePrintfBuffer( - hashedStrings, - printfBufferRead, - printfBufferElementSize); - - if (formatPrint.length != 0) - textResult += "Shader Output:\n" + formatPrint.join("") + "\n"; - - printfBufferRead.unmap(); - let printResult = document.getElementById("printResult") - if (!(printResult instanceof HTMLTextAreaElement)) { - throw new Error("printResult invalid type") - } - printResult.value = textResult; -} - function checkShaderType(userSource: string) { // we did a pre-filter on the user input source code. - const isImageMain = userSource.match("imageMain"); - const isPrintMain = userSource.match("printMain"); + let shaderTypes = RUNNABLE_ENTRY_POINT_NAMES.filter((entryPoint) => userSource.includes(entryPoint)); // Only one of the main function should be defined. // In this case, we will know that the shader is not runnable, so we can only compile it. - if (isImageMain == isPrintMain) - return SlangCompiler.NON_RUNNABLE_SHADER; + if (shaderTypes.length !== 1) + return null; - if (isImageMain) - return SlangCompiler.RENDER_SHADER; - else - return SlangCompiler.PRINT_SHADER; + return shaderTypes[0]; } export type ParsedCommand = { @@ -553,7 +517,7 @@ function safeSet(map: Map, key: str }; async function processResourceCommands(pipeline: ComputePipeline | GraphicsPipeline, resourceBindings: Bindings, resourceCommands: { resourceName: string; parsedCommand: ParsedCommand; }[]) { - var allocatedResources: Map = new Map(); + let allocatedResources: Map = new Map(); for (const { resourceName, parsedCommand } of resourceCommands) { if (parsedCommand.type === "ZEROS") { @@ -576,7 +540,7 @@ async function processResourceCommands(pipeline: ComputePipeline | GraphicsPipel // Initialize the buffer with zeros. let zeros: BufferSource; - if(elementSize == 4) { + if (elementSize == 4) { zeros = new Float32Array(parsedCommand.count); } else { throw new Error("Element size isn't handled") @@ -770,7 +734,7 @@ async function processResourceCommands(pipeline: ComputePipeline | GraphicsPipel usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST, })); - var length = new Float32Array(8).byteLength; + let length = new Float32Array(8).byteLength; safeSet(allocatedResources, "uniformInput", pipeline.device.createBuffer({ size: length, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST })); return allocatedResources; @@ -782,7 +746,7 @@ function freeAllocatedResources(resources: Map) } } -export var onRun = () => { +export let onRun = () => { if (!device) return; if (!monacoEditor) @@ -802,17 +766,17 @@ export var onRun = () => { // We will do a pre-filter on the user input source code, if it's not runnable, we will not run it. const userSource = monacoEditor.getValue(); const shaderType = checkShaderType(userSource); - if (shaderType == SlangCompiler.NON_RUNNABLE_SHADER) { - toggleDisplayMode(HIDDEN_MODE); + if (shaderType == null) { + toggleDisplayMode(null); setEditorValue(codeGenArea, ""); throw new Error("Error: In order to run the shader, please define either imageMain or printMain function in the shader code."); } - const entryPointName = shaderType == SlangCompiler.RENDER_SHADER ? "imageMain" : "printMain"; + const entryPointName = shaderType; const ret = compileShader(userSource, entryPointName, "WGSL"); if (!ret.succ) { - toggleDisplayMode(HIDDEN_MODE); + toggleDisplayMode(null); throw new Error(""); } @@ -887,11 +851,7 @@ export var onRun = () => { if (compiler == null) { throw new Error("Could not get compiler") } - if (compiler.shaderType == SlangCompiler.PRINT_SHADER) { - await printResult(); - return false; // Stop after one frame. - } - else if (compiler.shaderType == SlangCompiler.RENDER_SHADER) { + if (compiler.shaderType !== null) { return await execFrame(timeMS); } return false; @@ -906,7 +866,7 @@ export function compileOrRun() { const userSource = monacoEditor.getValue(); const shaderType = checkShaderType(userSource); - if (shaderType == SlangCompiler.NON_RUNNABLE_SHADER) { + if (shaderType == null) { onCompile(); } else { @@ -926,7 +886,7 @@ export function compileOrRun() { } } -var reflectionJson: any = {}; +let reflectionJson: any = {}; type Shader = { succ: true, @@ -976,7 +936,7 @@ function compileShader(userSource: string, entryPoint: string, compileTarget: st // have no way to call the user defined function, and compile engine cannot compile the source code. export async function onCompile() { - toggleDisplayMode(HIDDEN_MODE); + toggleDisplayMode(null); const compileTarget = targetSelect.value; await updateEntryPointOptions(); @@ -1050,7 +1010,7 @@ export function loadEditor(readOnlyMode = false, containerId: string, preloadCod // Event when loading the WebAssembly module -var moduleLoadingMessage = ""; +let moduleLoadingMessage = ""; type ReplaceReturnType any, TNewReturn> = (...a: Parameters) => TNewReturn; export type ModuleType = MainModule & Omit & { instantiateWasm: ReplaceReturnType> @@ -1070,7 +1030,7 @@ globalThis.Module = { }, instantiateWasm: async function (imports: WebAssembly.Imports, receiveInstance: (arg0: WebAssembly.Instance) => void): Promise { // Step 1: Fetch the compressed .wasm.gz file - var progressBar = document.getElementById('progress-bar'); + let progressBar = document.getElementById('progress-bar'); const compressedData = await fetchWithProgress('slang-wasm.wasm.gz', (loaded, total) => { const progress = (loaded / total) * 100; if (progressBar == null) progressBar = document.getElementById('progress-bar'); @@ -1086,12 +1046,12 @@ globalThis.Module = { return instance.exports; }, onRuntimeInitialized: function () { - var label = document.getElementById("loadingStatusLabel"); + let label = document.getElementById("loadingStatusLabel"); if (label) label.innerText = "Initializing Slang Compiler..."; try { compiler = new SlangCompiler(globalThis.Module); - var result = compiler.init(); + let result = compiler.init(); slangd = globalThis.Module.createLanguageServer(); if (result.ret) { (document.getElementById("compile-btn") as HTMLButtonElement).disabled = false; @@ -1115,7 +1075,7 @@ globalThis.Module = { RequireJS.require(["./slang-wasm.js"]); -var pageLoaded = false; +let pageLoaded = false; // event when loading the page window.onload = async function () { pageLoaded = true;