-
Notifications
You must be signed in to change notification settings - Fork 240
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add example showing how to use Slang/WASM (#5996)
This closes #5656.
- Loading branch information
Showing
4 changed files
with
214 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Simple WebGPU example using Slang WebAssembly library | ||
|
||
## Description | ||
|
||
This is a simple example showing how WebGPU applications can use the slang-wasm library to compile slang shaders at runtime to WGSL. | ||
The resulting application shows a green triangle rendered on a black background. | ||
|
||
## Instructions | ||
|
||
Follow the WebAssembly build instructions in `docs/building.md` to produce `slang-wasm.js` and `slang-wasm.wasm`, and place these files in this directory. | ||
|
||
Start a web server, for example by running the following command in this directory: | ||
|
||
$ python -m http.server | ||
|
||
Finally, visit `http://localhost:8000/` to see the application running in your browser. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
"use strict"; | ||
|
||
let Example = { | ||
initialize: async function (slang, canvas) { | ||
async function render(shaders) { | ||
if (!navigator.gpu) { | ||
throw new Error("WebGPU not supported on this browser."); | ||
} | ||
const adapter = await navigator.gpu.requestAdapter(); | ||
if (!adapter) { | ||
throw new Error("No appropriate GPUAdapter found."); | ||
} | ||
const device = await adapter.requestDevice(); | ||
const context = canvas.getContext("webgpu"); | ||
const canvasFormat = navigator.gpu.getPreferredCanvasFormat(); | ||
context.configure({ | ||
device: device, | ||
format: canvasFormat, | ||
}); | ||
|
||
const vertexBufferLayout = { | ||
arrayStride: 8, | ||
attributes: [{ | ||
format: "float32x2", | ||
offset: 0, | ||
shaderLocation: 0, | ||
}], | ||
}; | ||
|
||
const pipeline = device.createRenderPipeline({ | ||
label: "Pipeline", | ||
layout: "auto", | ||
vertex: { | ||
module: device.createShaderModule({ | ||
label: "Vertex shader module", | ||
code: shaders.vertex | ||
}), | ||
entryPoint: "vertexMain", | ||
buffers: [vertexBufferLayout] | ||
}, | ||
fragment: { | ||
module: device.createShaderModule({ | ||
label: "Fragment shader module", | ||
code: shaders.fragment | ||
}), | ||
entryPoint: "fragmentMain", | ||
targets: [{ | ||
format: canvasFormat | ||
}] | ||
} | ||
}); | ||
|
||
const vertices = new Float32Array([ | ||
0.0, -0.8, | ||
+0.8, +0.8, | ||
-0.8, +0.8, | ||
]); | ||
const vertexBuffer = device.createBuffer({ | ||
label: "Triangle vertices", | ||
size: vertices.byteLength, | ||
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, | ||
}); | ||
const bufferOffset = 0; | ||
device.queue.writeBuffer(vertexBuffer, bufferOffset, vertices); | ||
|
||
const encoder = device.createCommandEncoder(); | ||
const pass = encoder.beginRenderPass({ | ||
colorAttachments: [{ | ||
view: context.getCurrentTexture().createView(), | ||
loadOp: "clear", | ||
clearValue: { r: 0, g: 0, b: 0, a: 1 }, | ||
storeOp: "store", | ||
}] | ||
}); | ||
pass.setPipeline(pipeline); | ||
const vertexBufferSlot = 0; | ||
pass.setVertexBuffer(vertexBufferSlot, vertexBuffer); | ||
pass.draw(vertices.length / 2); | ||
pass.end(); | ||
const commandBuffer = encoder.finish(); | ||
device.queue.submit([commandBuffer]); | ||
} | ||
|
||
const slangCode = await fetch("shader.slang").then(r => r.text()); | ||
|
||
var wasmCompileTarget = null; | ||
var compileTargetMap = slang.module.getCompileTargets(); | ||
for (var i = 0; i < compileTargetMap.length; i++) { | ||
var target = compileTargetMap[i]; | ||
if(target.name == "WGSL") { | ||
wasmCompileTarget = target.value; | ||
} | ||
} | ||
if (wasmCompileTarget === null) { | ||
throw new Error("Slang/WASM module doesn't support WGSL compile target."); | ||
} | ||
|
||
var slangSession = slang.globalSession.createSession(wasmCompileTarget); | ||
if (!slangSession) { | ||
throw new Error("Failed to create global Slang session."); | ||
} | ||
|
||
var wgslShaders = null; | ||
try { | ||
var module = slangSession.loadModuleFromSource( | ||
slangCode, "shader", '/shader.slang' | ||
); | ||
var vertexEntryPoint = module.findAndCheckEntryPoint( | ||
"vertexMain", slang.constants.STAGE_VERTEX | ||
); | ||
var fragmentEntryPoint = module.findAndCheckEntryPoint( | ||
"fragmentMain", slang.constants.STAGE_FRAGMENT | ||
); | ||
var linkedProgram = slangSession.createCompositeComponentType([ | ||
module, vertexEntryPoint, fragmentEntryPoint | ||
]).link(); | ||
wgslShaders = { | ||
vertex: linkedProgram.getEntryPointCode( | ||
0 /* entryPointIndex */, 0 /* targetIndex */ | ||
), | ||
fragment: linkedProgram.getEntryPointCode( | ||
1 /* entryPointIndex */, 0 /* targetIndex */ | ||
), | ||
}; | ||
} finally { | ||
if (slangSession) { | ||
slangSession.delete(); | ||
} | ||
} | ||
|
||
if (!wgslShaders) { | ||
throw new Error("Failed to compile WGSL shaders."); | ||
} | ||
|
||
render(wgslShaders); | ||
} | ||
} | ||
|
||
var Module = { | ||
onRuntimeInitialized: function() { | ||
const canvas = document.querySelector("canvas"); | ||
|
||
var globalSlangSession = Module.createGlobalSession(); | ||
if (!globalSlangSession) { | ||
throw new Error("Failed to create global Slang session."); | ||
} | ||
|
||
const slang = { | ||
module: Module, | ||
globalSession: globalSlangSession, | ||
constants: { | ||
STAGE_VERTEX: 1, | ||
STAGE_FRAGMENT: 5, | ||
}, | ||
}; | ||
Example.initialize(slang, canvas); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<html> | ||
<head> | ||
<title>WebGPU Triangle using Slang WASM</title> | ||
<script src="example.js"></script> | ||
</head> | ||
<body> | ||
<center> | ||
<canvas width="512" height="512"></canvas> | ||
</center> | ||
<script src="slang-wasm.js"></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
struct VertexStageInput | ||
{ | ||
float4 position : POSITION0; | ||
}; | ||
|
||
struct VertexStageOutput | ||
{ | ||
float4 positionClipSpace : SV_POSITION; | ||
}; | ||
|
||
struct FragmentStageOutput | ||
{ | ||
float4 color : SV_TARGET; | ||
}; | ||
|
||
VertexStageOutput vertexMain(VertexStageInput input) : SV_Position | ||
{ | ||
VertexStageOutput output; | ||
output.positionClipSpace = float4(input.position.xy, 1); | ||
return output; | ||
} | ||
|
||
FragmentStageOutput fragmentMain() : SV_Target | ||
{ | ||
FragmentStageOutput output; | ||
output.color = float4(0, 1, 0, 1); | ||
return output; | ||
} |