Skip to content

Commit

Permalink
Add example showing how to use Slang/WASM (#5996)
Browse files Browse the repository at this point in the history
This closes #5656.
  • Loading branch information
aleino-nv authored Jan 7, 2025
1 parent 7d4142e commit 7e278c3
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 0 deletions.
16 changes: 16 additions & 0 deletions examples/wgpu-slang-wasm/README.md
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.
158 changes: 158 additions & 0 deletions examples/wgpu-slang-wasm/example.js
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);
},
};
12 changes: 12 additions & 0 deletions examples/wgpu-slang-wasm/index.html
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>
28 changes: 28 additions & 0 deletions examples/wgpu-slang-wasm/shader.slang
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;
}

0 comments on commit 7e278c3

Please sign in to comment.