main.odin ¶
1package vendor_wgpu_example_triangle
2
3import "base:runtime"
4
5import "core:fmt"
6
7import "vendor:wgpu"
8
9state: struct {
10 ctx: runtime.Context,
11 os: OS,
12
13 instance: wgpu.Instance,
14 surface: wgpu.Surface,
15 adapter: wgpu.Adapter,
16 device: wgpu.Device,
17 config: wgpu.SurfaceConfiguration,
18 queue: wgpu.Queue,
19 module: wgpu.ShaderModule,
20 pipeline_layout: wgpu.PipelineLayout,
21 pipeline: wgpu.RenderPipeline,
22}
23
24main :: proc() {
25 state.ctx = context
26
27 os_init()
28
29 state.instance = wgpu.CreateInstance(nil)
30 if state.instance == nil {
31 panic("WebGPU is not supported")
32 }
33 state.surface = os_get_surface(state.instance)
34
35 wgpu.InstanceRequestAdapter(state.instance, &{ compatibleSurface = state.surface }, { callback = on_adapter })
36
37 on_adapter :: proc "c" (status: wgpu.RequestAdapterStatus, adapter: wgpu.Adapter, message: string, userdata1: rawptr, userdata2: rawptr) {
38 context = state.ctx
39 if status != .Success || adapter == nil {
40 fmt.panicf("request adapter failure: [%v] %s", status, message)
41 }
42 state.adapter = adapter
43 wgpu.AdapterRequestDevice(adapter, nil, { callback = on_device })
44 }
45
46 on_device :: proc "c" (status: wgpu.RequestDeviceStatus, device: wgpu.Device, message: string, userdata1: rawptr, userdata2: rawptr) {
47 context = state.ctx
48 if status != .Success || device == nil {
49 fmt.panicf("request device failure: [%v] %s", status, message)
50 }
51 state.device = device
52
53 width, height := os_get_framebuffer_size()
54
55 state.config = wgpu.SurfaceConfiguration {
56 device = state.device,
57 usage = { .RenderAttachment },
58 format = .BGRA8Unorm,
59 width = width,
60 height = height,
61 presentMode = .Fifo,
62 alphaMode = .Opaque,
63 }
64 wgpu.SurfaceConfigure(state.surface, &state.config)
65
66 state.queue = wgpu.DeviceGetQueue(state.device)
67
68 shader :: `
69 @vertex
70 fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4<f32> {
71 let x = f32(i32(in_vertex_index) - 1);
72 let y = f32(i32(in_vertex_index & 1u) * 2 - 1);
73 return vec4<f32>(x, y, 0.0, 1.0);
74 }
75
76 @fragment
77 fn fs_main() -> @location(0) vec4<f32> {
78 return vec4<f32>(1.0, 0.0, 0.0, 1.0);
79 }`
80
81 state.module = wgpu.DeviceCreateShaderModule(state.device, &{
82 nextInChain = &wgpu.ShaderSourceWGSL{
83 sType = .ShaderSourceWGSL,
84 code = shader,
85 },
86 })
87
88 state.pipeline_layout = wgpu.DeviceCreatePipelineLayout(state.device, &{})
89 state.pipeline = wgpu.DeviceCreateRenderPipeline(state.device, &{
90 layout = state.pipeline_layout,
91 vertex = {
92 module = state.module,
93 entryPoint = "vs_main",
94 },
95 fragment = &{
96 module = state.module,
97 entryPoint = "fs_main",
98 targetCount = 1,
99 targets = &wgpu.ColorTargetState{
100 format = .BGRA8Unorm,
101 writeMask = wgpu.ColorWriteMaskFlags_All,
102 },
103 },
104 primitive = {
105 topology = .TriangleList,
106
107 },
108 multisample = {
109 count = 1,
110 mask = 0xFFFFFFFF,
111 },
112 })
113
114 os_run()
115 }
116}
117
118resize :: proc "c" () {
119 context = state.ctx
120
121 state.config.width, state.config.height = os_get_framebuffer_size()
122 wgpu.SurfaceConfigure(state.surface, &state.config)
123}
124
125frame :: proc "c" (dt: f32) {
126 context = state.ctx
127
128 surface_texture := wgpu.SurfaceGetCurrentTexture(state.surface)
129 switch surface_texture.status {
130 case .SuccessOptimal, .SuccessSuboptimal:
131
132 case .Timeout, .Outdated, .Lost:
133
134 if surface_texture.texture != nil {
135 wgpu.TextureRelease(surface_texture.texture)
136 }
137 resize()
138 return
139 case .Occluded:
140
141 return
142 case .Error:
143
144 fmt.panicf("[triangle] get_current_texture status=%v", surface_texture.status)
145 }
146 defer wgpu.TextureRelease(surface_texture.texture)
147
148 frame := wgpu.TextureCreateView(surface_texture.texture, nil)
149 defer wgpu.TextureViewRelease(frame)
150
151 command_encoder := wgpu.DeviceCreateCommandEncoder(state.device, nil)
152 defer wgpu.CommandEncoderRelease(command_encoder)
153
154 render_pass_encoder := wgpu.CommandEncoderBeginRenderPass(
155 command_encoder, &{
156 colorAttachmentCount = 1,
157 colorAttachments = &wgpu.RenderPassColorAttachment{
158 view = frame,
159 loadOp = .Clear,
160 storeOp = .Store,
161 depthSlice = wgpu.DEPTH_SLICE_UNDEFINED,
162 clearValue = { 0, 1, 0, 1 },
163 },
164 },
165 )
166
167 wgpu.RenderPassEncoderSetPipeline(render_pass_encoder, state.pipeline)
168 wgpu.RenderPassEncoderDraw(render_pass_encoder, vertexCount=3, instanceCount=1, firstVertex=0, firstInstance=0)
169
170 wgpu.RenderPassEncoderEnd(render_pass_encoder)
171 wgpu.RenderPassEncoderRelease(render_pass_encoder)
172
173 command_buffer := wgpu.CommandEncoderFinish(command_encoder, nil)
174 defer wgpu.CommandBufferRelease(command_buffer)
175
176 wgpu.QueueSubmit(state.queue, { command_buffer })
177 wgpu.SurfacePresent(state.surface)
178}
179
180finish :: proc() {
181 wgpu.RenderPipelineRelease(state.pipeline)
182 wgpu.PipelineLayoutRelease(state.pipeline_layout)
183 wgpu.ShaderModuleRelease(state.module)
184 wgpu.QueueRelease(state.queue)
185 wgpu.DeviceRelease(state.device)
186 wgpu.AdapterRelease(state.adapter)
187 wgpu.SurfaceRelease(state.surface)
188 wgpu.InstanceRelease(state.instance)
189}
1#+build !js
2package vendor_wgpu_example_triangle
3
4import "core:time"
5
6import "vendor:glfw"
7import "vendor:wgpu"
8import "vendor:wgpu/glfwglue"
9
10OS :: struct {
11 window: glfw.WindowHandle,
12}
13
14os_init :: proc() {
15 if !glfw.Init() {
16 panic("[glfw] init failure")
17 }
18
19 glfw.WindowHint(glfw.CLIENT_API, glfw.NO_API)
20 state.os.window = glfw.CreateWindow(960, 540, "WGPU Native Triangle", nil, nil)
21
22 glfw.SetFramebufferSizeCallback(state.os.window, size_callback)
23}
24
25os_run :: proc() {
26 dt: f32
27
28 for !glfw.WindowShouldClose(state.os.window) {
29 start := time.tick_now()
30
31 glfw.PollEvents()
32 frame(dt)
33
34 dt = f32(time.duration_seconds(time.tick_since(start)))
35 }
36
37 finish()
38
39 glfw.DestroyWindow(state.os.window)
40 glfw.Terminate()
41}
42
43os_get_framebuffer_size :: proc() -> (width, height: u32) {
44 iw, ih := glfw.GetFramebufferSize(state.os.window)
45 return u32(iw), u32(ih)
46}
47
48os_get_surface :: proc(instance: wgpu.Instance) -> wgpu.Surface {
49 return glfwglue.GetSurface(instance, state.os.window)
50}
51
52@(private="file")
53size_callback :: proc "c" (window: glfw.WindowHandle, width, height: i32) {
54 resize()
55}
1package vendor_wgpu_example_triangle
2
3import "base:runtime"
4
5import "core:sys/wasm/js"
6
7import "vendor:wgpu"
8
9OS :: struct {
10 initialized: bool,
11}
12
13os_init :: proc() {
14 ok := js.add_window_event_listener(.Resize, nil, size_callback)
15 assert(ok)
16}
17
18
19os_run :: proc() {
20 state.os.initialized = true
21}
22
23@(private="file", export)
24step :: proc(dt: f32) -> bool {
25 if !state.os.initialized {
26 return true
27 }
28
29 frame(dt)
30 return true
31}
32
33os_get_framebuffer_size :: proc() -> (width, height: u32) {
34 rect := js.get_bounding_client_rect("body")
35 dpi := js.device_pixel_ratio()
36 return u32(f64(rect.width) * dpi), u32(f64(rect.height) * dpi)
37}
38
39os_get_surface :: proc(instance: wgpu.Instance) -> wgpu.Surface {
40 return wgpu.InstanceCreateSurface(
41 instance,
42 &wgpu.SurfaceDescriptor{
43 nextInChain = &wgpu.SurfaceSourceCanvasHTMLSelector{
44 sType = .SurfaceSourceCanvasHTMLSelector,
45 selector = "#wgpu-canvas",
46 },
47 },
48 )
49}
50
51@(private="file", fini)
52os_fini :: proc "contextless" () {
53 context = runtime.default_context()
54 js.remove_window_event_listener(.Resize, nil, size_callback)
55
56 finish()
57}
58
59@(private="file")
60size_callback :: proc(e: js.Event) {
61 resize()
62}