Created
June 3, 2023 21:07
-
-
Save jonahwilliams/65a3f507856bab523ca2d8651cd0d298 to your computer and use it in GitHub Desktop.
WebGPU + WASM hello world
This file contains hidden or 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
@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