Skip to content

Instantly share code, notes, and snippets.

@jonahwilliams
Created June 3, 2023 21:07
Show Gist options
  • Save jonahwilliams/65a3f507856bab523ca2d8651cd0d298 to your computer and use it in GitHub Desktop.
Save jonahwilliams/65a3f507856bab523ca2d8651cd0d298 to your computer and use it in GitHub Desktop.
WebGPU + WASM hello world
@staticInterop
import 'dart:js_interop';
@JS()
@staticInterop
class Element {}
@JS()
@staticInterop
class Canvas extends Element {}
extension CanvasHelper on Canvas {
external WebGPUCanvasContext getContext(JSString id);
}
extension Helper<T> on Future<T> {
Future<S> cast<S>() => then((value) => value as S);
}
@JS()
@staticInterop
class WebGPUCanvasContext {}
extension WebGPUCanvasContextHelpers on WebGPUCanvasContext {
external void configure(JSAny? args);
external WebGPUTexture getCurrentTexture();
}
@JS()
@staticInterop
class WebGPUTexture {}
extension WebGPUTextureHelpers on WebGPUTexture {
external WebGPUTextureView createView([JSAny? args]);
}
@JS()
@staticInterop
class WebGPUTextureView {}
extension ElementHelpers on Element {
external JSNumber width;
external JSNumber height;
}
@JS()
@staticInterop
class Document {}
extension DocumentHelpers on Document {
@JS()
@staticInterop
external Element getElementById(JSString id);
}
@JS()
@staticInterop
external Document get document;
@JS()
@staticInterop
external Navigator get navigator;
@JS()
@staticInterop
class Navigator {}
extension NavigatorHelpers on Navigator {
external WebGPUContext? get gpu;
}
@JS()
@staticInterop
class WebGPUContext {}
extension WebGPUContextHelpers on WebGPUContext {
external JSPromise requestAdapter();
Future<WebGPUAdapter?> fetchAdapter() async {
return requestAdapter().toDart.cast<WebGPUAdapter?>();
}
external JSString getPreferredCanvasFormat();
}
@JS()
@staticInterop
class WebGPUAdapter {}
extension WebGPUAdapterHelpers on WebGPUAdapter {
external JSPromise requestDevice();
Future<WebGPUDevice?> fetchDevice() async {
return requestDevice().toDart.cast<WebGPUDevice?>();
}
}
@JS()
@staticInterop
class WebGPUDevice {}
extension WebGPUDeviceHelpers on WebGPUDevice {
external WebGPUShaderModule createShaderModule(JSAny? map);
external WebGPURenderPipeline createRenderPipeline(JSAny? map);
external WebGPUCommandEncoder createCommandEncoder(JSAny? map);
external WebGPUDeviceQueue get queue;
}
@JS()
@staticInterop
class WebGPUCommandEncoder {}
extension WebGPUCommandEncoderHelpers on WebGPUCommandEncoder {
external WebGPURenderPass beginRenderPass(JSAny? map);
external WebGPUCommandBuffer finish();
}
@JS()
@staticInterop
class WebGPURenderPass {}
extension WebGPURenderPassHelpers on WebGPURenderPass {
external void setPipeline(WebGPURenderPipeline pipeline);
external void draw(JSNumber count);
external void end();
}
@JS()
@staticInterop
class WebGPUShaderModule {}
@JS()
@staticInterop
class WebGPURenderPipeline {}
@JS()
@staticInterop
class WebGPUCommandBuffer {}
@JS()
@staticInterop
class WebGPUDeviceQueue {}
extension WebGPUDeviceQueueHelpers on WebGPUDeviceQueue {
external void submit(JSAny? buffers);
}
void main(List<String> args) async {
var canvas = document.getElementById('canvas'.toJS) as Canvas;
var context = canvas.getContext('webgpu'.toJS);
if (navigator.gpu == null) {
return;
}
var adapter = await navigator.gpu!.fetchAdapter();
var device = await adapter?.fetchDevice();
var format = navigator.gpu!.getPreferredCanvasFormat();
context.configure({
'device': device!,
'format': format,
}.jsify());
var module = device.createShaderModule({
'label': 'our hardcoded red triangle shaders',
'code': '''
@vertex fn vs(
@builtin(vertex_index) vertexIndex : u32
) -> @builtin(position) vec4f {
var pos = array<vec2f, 3>(
vec2f( 0.0, 0.5), // top center
vec2f(-0.5, -0.5), // bottom left
vec2f( 0.5, -0.5) // bottom right
);
return vec4f(pos[vertexIndex], 0.0, 1.0);
}
@fragment fn fs() -> @location(0) vec4f {
return vec4f(1.0, 0.0, 0.0, 1.0);
}
''',
}.jsify());
var pipeline = device.createRenderPipeline({
'label': 'our hardcoded red triangle pipeline',
'layout': 'auto',
'vertex': {
'module': module,
'entryPoint': 'vs',
},
'fragment': {
'module': module,
'entryPoint': 'fs',
'targets': [
{'format': format}
],
},
}.jsify());
var color0 = {
'clearValue': [0.3, 0.3, 0.3, 1],
'loadOp': 'clear',
'storeOp': 'store',
};
var renderPassDescriptor = {
'label': 'our basic canvas renderPass',
'colorAttachments': [
color0,
],
};
void render() {
color0['view'] = context.getCurrentTexture().createView();
var encoder = device.createCommandEncoder({'label': 'our encoder'}.jsify());
var pass = encoder.beginRenderPass(renderPassDescriptor.jsify());
pass.setPipeline(pipeline);
pass.draw(3.toJS); // call our vertex shader 3 times
pass.end();
var commandBuffer = encoder.finish();
device.queue.submit([commandBuffer].jsify());
}
render();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment