Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add example showing how to use Slang/WASM #5996

Merged
merged 3 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is render() gets called only once?
Or does it get called every frame?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just called once, since there is no animation in the example.

}
}

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;
}
Loading