wgpu/glfw-triangle Web

Code

main.odin ¶
189 linesSource

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		// All good, could handle suboptimal here.
132	case .Timeout, .Outdated, .Lost:
133		// Skip this frame, and re-configure surface.
134		if surface_texture.texture != nil {
135			wgpu.TextureRelease(surface_texture.texture)
136		}
137		resize()
138		return
139	case .Occluded:
140		// Window is occluded (e.g. minimized), skip this frame.
141		return
142	case .Error:
143		// Fatal error
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}

os_glfw.odin ¶ !js
55 linesSource

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}

os_js.odin ¶
62 linesSource

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// NOTE: frame loop is done by the runtime.js repeatedly calling `step`.
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}

To Build and Run 3

Declarations Used 74