main.odin ¶
1package vendor_wgpu_example_microui
2
3import "base:runtime"
4
5import "core:fmt"
6
7import mu "vendor:microui"
8
9state := struct {
10 ctx: runtime.Context,
11
12 mu_ctx: mu.Context,
13 log_buf: [1<<16]byte,
14 log_buf_len: int,
15 log_buf_updated: bool,
16 bg: mu.Color,
17
18 os: OS,
19 renderer: Renderer,
20
21 cursor: [2]i32,
22}{
23 bg = {90, 95, 100, 255},
24}
25
26main :: proc() {
27 state.ctx = context
28
29 mu.init(&state.mu_ctx, os_set_clipboard, os_get_clipboard, nil)
30 state.mu_ctx.text_width = mu.default_atlas_text_width
31 state.mu_ctx.text_height = mu.default_atlas_text_height
32
33 os_init()
34 r_init_and_run()
35}
36
37frame :: proc(dt: f32) {
38 free_all(context.temp_allocator)
39
40 mc := &state.mu_ctx
41
42 mu.begin(mc)
43
44 demo_windows(mc)
45
46 mu.end(mc)
47
48 r_render()
49}
50
51u8_slider :: proc(ctx: ^mu.Context, val: ^u8, lo, hi: u8) -> (res: mu.Result_Set) {
52 mu.push_id(ctx, uintptr(val))
53
54 @static tmp: mu.Real
55 tmp = mu.Real(val^)
56 res = mu.slider(ctx, &tmp, mu.Real(lo), mu.Real(hi), 0, "%.0f", {.ALIGN_CENTER})
57 val^ = u8(tmp)
58 mu.pop_id(ctx)
59 return
60}
61
62write_log :: proc(str: string) {
63 state.log_buf_len += copy(state.log_buf[state.log_buf_len:], str)
64 state.log_buf_len += copy(state.log_buf[state.log_buf_len:], "\n")
65 state.log_buf_updated = true
66}
67
68read_log :: proc() -> string {
69 return string(state.log_buf[:state.log_buf_len])
70}
71reset_log :: proc() {
72 state.log_buf_updated = true
73 state.log_buf_len = 0
74}
75
76demo_windows :: proc(ctx: ^mu.Context) {
77 @static opts := mu.Options{.NO_CLOSE}
78
79 if mu.window(ctx, "Demo Window", {40, 40, 300, 450}, opts) {
80 if .ACTIVE in mu.header(ctx, "Window Info") {
81 win := mu.get_current_container(ctx)
82 mu.layout_row(ctx, {54, -1}, 0)
83 mu.label(ctx, "Position:")
84 mu.label(ctx, fmt.tprintf("%d, %d", win.rect.x, win.rect.y))
85 mu.label(ctx, "Size:")
86 mu.label(ctx, fmt.tprintf("%d, %d", win.rect.w, win.rect.h))
87 }
88
89 if .ACTIVE in mu.header(ctx, "Window Options") {
90 mu.layout_row(ctx, {120, 120, 120}, 0)
91 for opt in mu.Opt {
92 state := opt in opts
93 if .CHANGE in mu.checkbox(ctx, fmt.tprintf("%v", opt), &state) {
94 if state {
95 opts += {opt}
96 } else {
97 opts -= {opt}
98 }
99 }
100 }
101 }
102
103 if .ACTIVE in mu.header(ctx, "Test Buttons", {.EXPANDED}) {
104 mu.layout_row(ctx, {86, -110, -1})
105 mu.label(ctx, "Test buttons 1:")
106 if .SUBMIT in mu.button(ctx, "Button 1") { write_log("Pressed button 1") }
107 if .SUBMIT in mu.button(ctx, "Button 2") { write_log("Pressed button 2") }
108 mu.label(ctx, "Test buttons 2:")
109 if .SUBMIT in mu.button(ctx, "Button 3") { write_log("Pressed button 3") }
110 if .SUBMIT in mu.button(ctx, "Button 4") { write_log("Pressed button 4") }
111 }
112
113 if .ACTIVE in mu.header(ctx, "Tree and Text", {.EXPANDED}) {
114 mu.layout_row(ctx, {140, -1})
115 mu.layout_begin_column(ctx)
116 if .ACTIVE in mu.treenode(ctx, "Test 1") {
117 if .ACTIVE in mu.treenode(ctx, "Test 1a") {
118 mu.label(ctx, "Hello")
119 mu.label(ctx, "world")
120 }
121 if .ACTIVE in mu.treenode(ctx, "Test 1b") {
122 if .SUBMIT in mu.button(ctx, "Button 1") { write_log("Pressed button 1") }
123 if .SUBMIT in mu.button(ctx, "Button 2") { write_log("Pressed button 2") }
124 }
125 }
126 if .ACTIVE in mu.treenode(ctx, "Test 2") {
127 mu.layout_row(ctx, {53, 53})
128 if .SUBMIT in mu.button(ctx, "Button 3") { write_log("Pressed button 3") }
129 if .SUBMIT in mu.button(ctx, "Button 4") { write_log("Pressed button 4") }
130 if .SUBMIT in mu.button(ctx, "Button 5") { write_log("Pressed button 5") }
131 if .SUBMIT in mu.button(ctx, "Button 6") { write_log("Pressed button 6") }
132 }
133 if .ACTIVE in mu.treenode(ctx, "Test 3") {
134 @static checks := [3]bool{true, false, true}
135 mu.checkbox(ctx, "Checkbox 1", &checks[0])
136 mu.checkbox(ctx, "Checkbox 2", &checks[1])
137 mu.checkbox(ctx, "Checkbox 3", &checks[2])
138
139 }
140 mu.layout_end_column(ctx)
141
142 mu.layout_begin_column(ctx)
143 mu.layout_row(ctx, {-1})
144 mu.text(ctx,
145 "Lorem ipsum dolor sit amet, consectetur adipiscing "+
146 "elit. Maecenas lacinia, sem eu lacinia molestie, mi risus faucibus "+
147 "ipsum, eu varius magna felis a nulla.",
148 )
149 mu.layout_end_column(ctx)
150 }
151
152 if .ACTIVE in mu.header(ctx, "Background Colour", {.EXPANDED}) {
153 mu.layout_row(ctx, {-78, -1}, 68)
154 mu.layout_begin_column(ctx)
155 {
156 mu.layout_row(ctx, {46, -1}, 0)
157 mu.label(ctx, "Red:"); u8_slider(ctx, &state.bg.r, 0, 255)
158 mu.label(ctx, "Green:"); u8_slider(ctx, &state.bg.g, 0, 255)
159 mu.label(ctx, "Blue:"); u8_slider(ctx, &state.bg.b, 0, 255)
160 }
161 mu.layout_end_column(ctx)
162
163 r := mu.layout_next(ctx)
164 mu.draw_rect(ctx, r, state.bg)
165 mu.draw_box(ctx, mu.expand_rect(r, 1), ctx.style.colors[.BORDER])
166 mu.draw_control_text(ctx, fmt.tprintf("#%02x%02x%02x", state.bg.r, state.bg.g, state.bg.b), r, .TEXT, {.ALIGN_CENTER})
167 }
168 }
169
170 if mu.window(ctx, "Log Window", {350, 40, 300, 200}, opts) {
171 mu.layout_row(ctx, {-1}, -28)
172 mu.begin_panel(ctx, "Log")
173 mu.layout_row(ctx, {-1}, -1)
174 mu.text(ctx, read_log())
175 if state.log_buf_updated {
176 panel := mu.get_current_container(ctx)
177 panel.scroll.y = panel.content_size.y
178 state.log_buf_updated = false
179 }
180 mu.end_panel(ctx)
181
182 @static buf: [128]byte
183 @static buf_len: int
184 submitted := false
185 mu.layout_row(ctx, {-70, -1})
186 if .SUBMIT in mu.textbox(ctx, buf[:], &buf_len) {
187 mu.set_focus(ctx, ctx.last_id)
188 submitted = true
189 }
190 if .SUBMIT in mu.button(ctx, "Submit") {
191 submitted = true
192 }
193 if submitted {
194 write_log(string(buf[:buf_len]))
195 buf_len = 0
196 }
197 }
198
199 if mu.window(ctx, "Style Window", {350, 250, 300, 240}) {
200 @static colors := [mu.Color_Type]string{
201 .TEXT = "text",
202 .BORDER = "border",
203 .WINDOW_BG = "window bg",
204 .TITLE_BG = "title bg",
205 .TITLE_TEXT = "title text",
206 .PANEL_BG = "panel bg",
207 .BUTTON = "button",
208 .BUTTON_HOVER = "button hover",
209 .BUTTON_FOCUS = "button focus",
210 .BASE = "base",
211 .BASE_HOVER = "base hover",
212 .BASE_FOCUS = "base focus",
213 .SCROLL_BASE = "scroll base",
214 .SCROLL_THUMB = "scroll thumb",
215 .SELECTION_BG = "selection bg",
216 }
217
218 sw := i32(f32(mu.get_current_container(ctx).body.w) * 0.14)
219 mu.layout_row(ctx, {80, sw, sw, sw, sw, -1})
220 for label, col in colors {
221 mu.label(ctx, label)
222 u8_slider(ctx, &ctx.style.colors[col].r, 0, 255)
223 u8_slider(ctx, &ctx.style.colors[col].g, 0, 255)
224 u8_slider(ctx, &ctx.style.colors[col].b, 0, 255)
225 u8_slider(ctx, &ctx.style.colors[col].a, 0, 255)
226 mu.draw_rect(ctx, mu.layout_next(ctx), ctx.style.colors[col])
227 }
228 }
229}
os_js.odin ¶
1#+feature dynamic-literals
2package vendor_wgpu_example_microui
3
4import "core:sys/wasm/js"
5import "core:unicode"
6import "core:unicode/utf8"
7
8import "vendor:wgpu"
9import mu "vendor:microui"
10
11OS :: struct {
12 initialized: bool,
13 clipboard: [dynamic]byte,
14}
15
16os_init :: proc() {
17 state.os.clipboard.allocator = context.allocator
18 assert(js.add_window_event_listener(.Key_Down, nil, key_down_callback))
19 assert(js.add_window_event_listener(.Key_Up, nil, key_up_callback))
20 assert(js.add_window_event_listener(.Mouse_Down, nil, mouse_down_callback))
21 assert(js.add_window_event_listener(.Mouse_Up, nil, mouse_up_callback))
22 assert(js.add_event_listener("wgpu-canvas", .Mouse_Move, nil, mouse_move_callback))
23 assert(js.add_window_event_listener(.Wheel, nil, scroll_callback))
24 assert(js.add_window_event_listener(.Resize, nil, size_callback))
25 assert(js.add_window_event_listener(.Blur, nil, blur_callback))
26}
27
28
29os_run :: proc() {
30 state.os.initialized = true
31}
32
33@(private="file", export)
34step :: proc(dt: f32) -> bool {
35 context = state.ctx
36
37 if !state.os.initialized {
38 return true
39 }
40
41 frame(dt)
42 return true
43}
44
45
46os_fini :: proc() {
47 js.remove_window_event_listener(.Key_Down, nil, key_down_callback)
48 js.remove_window_event_listener(.Key_Up, nil, key_up_callback)
49 js.remove_window_event_listener(.Mouse_Down, nil, mouse_down_callback)
50 js.remove_window_event_listener(.Mouse_Up, nil, mouse_up_callback)
51 js.remove_event_listener("wgpu-canvas", .Mouse_Move, nil, mouse_move_callback)
52 js.remove_window_event_listener(.Wheel, nil, scroll_callback)
53 js.remove_window_event_listener(.Resize, nil, size_callback)
54 js.remove_window_event_listener(.Blur, nil, blur_callback)
55}
56
57os_get_render_bounds :: proc() -> (width, height: u32) {
58 rect := js.get_bounding_client_rect("body")
59 dpi := os_get_dpi()
60 return u32(f32(rect.width) * dpi), u32(f32(rect.height) * dpi)
61}
62
63os_get_dpi :: proc() -> f32 {
64 ratio := f32(js.device_pixel_ratio())
65 return ratio
66}
67
68os_get_surface :: proc(instance: wgpu.Instance) -> wgpu.Surface {
69 return wgpu.InstanceCreateSurface(
70 instance,
71 &wgpu.SurfaceDescriptor{
72 nextInChain = &wgpu.SurfaceSourceCanvasHTMLSelector{
73 sType = .SurfaceSourceCanvasHTMLSelector,
74 selector = "#wgpu-canvas",
75 },
76 },
77 )
78}
79
80os_set_clipboard :: proc(_: rawptr, text: string) -> bool {
81
82 clear(&state.os.clipboard)
83 append(&state.os.clipboard, text)
84 return true
85}
86
87os_get_clipboard :: proc(_: rawptr) -> (string, bool) {
88
89 return string(state.os.clipboard[:]), true
90}
91
92@(private="file")
93KEY_MAP := map[string]mu.Key{
94 "ShiftLeft" = .SHIFT,
95 "ShiftRight" = .SHIFT,
96 "ControlLeft" = .CTRL,
97 "ControlRight" = .CTRL,
98 "MetaLeft" = .CTRL,
99 "MetaRight" = .CTRL,
100 "AltLeft" = .ALT,
101 "AltRight" = .ALT,
102 "Backspace" = .BACKSPACE,
103 "Delete" = .DELETE,
104 "Enter" = .RETURN,
105 "ArrowLeft" = .LEFT,
106 "ArrowRight" = .RIGHT,
107 "Home" = .HOME,
108 "End" = .END,
109 "KeyA" = .A,
110 "KeyX" = .X,
111 "KeyC" = .C,
112 "KeyV" = .V,
113}
114
115@(private="file")
116key_down_callback :: proc(e: js.Event) {
117 context = state.ctx
118
119 js.event_prevent_default()
120
121 if k, ok := KEY_MAP[e.data.key.code]; ok {
122 mu.input_key_down(&state.mu_ctx, k)
123 }
124
125 if .CTRL in state.mu_ctx.key_down_bits {
126 return
127 }
128
129 ch, size := utf8.decode_rune(e.data.key.key)
130 if len(e.data.key.key) == size && unicode.is_print(ch) {
131 mu.input_text(&state.mu_ctx, e.data.key.key)
132 }
133}
134
135@(private="file")
136key_up_callback :: proc(e: js.Event) {
137 context = state.ctx
138
139 if k, ok := KEY_MAP[e.data.key.code]; ok {
140 mu.input_key_up(&state.mu_ctx, k)
141 }
142
143 js.event_prevent_default()
144}
145
146@(private="file")
147mouse_down_callback :: proc(e: js.Event) {
148 context = state.ctx
149
150 switch e.data.mouse.button {
151 case 0: mu.input_mouse_down(&state.mu_ctx, state.cursor.x, state.cursor.y, .LEFT)
152 case 1: mu.input_mouse_down(&state.mu_ctx, state.cursor.x, state.cursor.y, .MIDDLE)
153 case 2: mu.input_mouse_down(&state.mu_ctx, state.cursor.x, state.cursor.y, .RIGHT)
154 }
155
156 js.event_prevent_default()
157}
158
159@(private="file")
160mouse_up_callback :: proc(e: js.Event) {
161 context = state.ctx
162
163 switch e.data.mouse.button {
164 case 0: mu.input_mouse_up(&state.mu_ctx, state.cursor.x, state.cursor.y, .LEFT)
165 case 1: mu.input_mouse_up(&state.mu_ctx, state.cursor.x, state.cursor.y, .MIDDLE)
166 case 2: mu.input_mouse_up(&state.mu_ctx, state.cursor.x, state.cursor.y, .RIGHT)
167 }
168
169 js.event_prevent_default()
170}
171
172@(private="file")
173mouse_move_callback :: proc(e: js.Event) {
174 context = state.ctx
175 state.cursor = {i32(e.data.mouse.offset.x), i32(e.data.mouse.offset.y)}
176 mu.input_mouse_move(&state.mu_ctx, state.cursor.x, state.cursor.y)
177}
178
179@(private="file")
180scroll_callback :: proc(e: js.Event) {
181 context = state.ctx
182 mu.input_scroll(&state.mu_ctx, i32(e.data.wheel.delta.x), i32(e.data.wheel.delta.y))
183}
184
185@(private="file")
186size_callback :: proc(e: js.Event) {
187 context = state.ctx
188 r_resize()
189}
190
191@(private="file")
192blur_callback :: proc(e: js.Event) {
193
194
195 state.mu_ctx.mouse_released_bits = state.mu_ctx.mouse_down_bits
196 state.mu_ctx.mouse_down_bits = {}
197
198 state.mu_ctx.key_down_bits = {}
199}
1package vendor_wgpu_example_microui
2
3import intr "base:intrinsics"
4
5import "core:fmt"
6import "core:math/linalg"
7
8import mu "vendor:microui"
9import "vendor:wgpu"
10
11BUFFER_SIZE :: 16384
12
13Renderer :: struct {
14 instance: wgpu.Instance,
15 surface: wgpu.Surface,
16 adapter: wgpu.Adapter,
17 device: wgpu.Device,
18 config: wgpu.SurfaceConfiguration,
19
20 module: wgpu.ShaderModule,
21
22 atlas_texture: wgpu.Texture,
23 atlas_texture_view: wgpu.TextureView,
24
25 pipeline_layout: wgpu.PipelineLayout,
26 pipeline: wgpu.RenderPipeline,
27
28 const_buffer: wgpu.Buffer,
29
30 tex_buffer: wgpu.Buffer,
31 vertex_buffer: wgpu.Buffer,
32 color_buffer: wgpu.Buffer,
33 index_buffer: wgpu.Buffer,
34
35 sampler: wgpu.Sampler,
36
37 bind_group_layout: wgpu.BindGroupLayout,
38 bind_group: wgpu.BindGroup,
39
40 queue: wgpu.Queue,
41
42 curr_encoder: wgpu.CommandEncoder,
43 curr_pass: wgpu.RenderPassEncoder,
44 curr_texture: wgpu.SurfaceTexture,
45 curr_view: wgpu.TextureView,
46
47 tex_buf: [BUFFER_SIZE * 8]f32,
48 vert_buf: [BUFFER_SIZE * 8]f32,
49 color_buf: [BUFFER_SIZE * 16]u8,
50 index_buf: [BUFFER_SIZE * 6]u32,
51 prev_buf_idx: u32,
52 buf_idx: u32,
53}
54
55r_init_and_run :: proc() {
56 r := &state.renderer
57
58 r.instance = wgpu.CreateInstance(nil)
59 r.surface = os_get_surface(r.instance)
60
61 wgpu.InstanceRequestAdapter(r.instance, &{ compatibleSurface = r.surface }, { callback = handle_request_adapter })
62}
63
64@(private="file")
65handle_request_adapter :: proc "c" (status: wgpu.RequestAdapterStatus, adapter: wgpu.Adapter, message: string, userdata1, userdata2: rawptr) {
66 context = state.ctx
67 if status != .Success || adapter == nil {
68 fmt.panicf("request adapter failure: [%v] %s", status, message)
69 }
70 state.renderer.adapter = adapter
71 wgpu.AdapterRequestDevice(adapter, nil, { callback = handle_request_device })
72}
73
74@(private="file")
75handle_request_device :: proc "c" (status: wgpu.RequestDeviceStatus, device: wgpu.Device, message: string, userdata1, userdata2: rawptr) {
76 context = state.ctx
77 if status != .Success || device == nil {
78 fmt.panicf("request device failure: [%v] %s", status, message)
79 }
80 state.renderer.device = device
81 on_adapter_and_device()
82}
83
84@(private="file")
85on_adapter_and_device :: proc() {
86 r := &state.renderer
87
88 width, height := os_get_render_bounds()
89
90 r.config = wgpu.SurfaceConfiguration {
91 device = r.device,
92 usage = { .RenderAttachment },
93 format = .BGRA8Unorm,
94 width = width,
95 height = height,
96 presentMode = .Fifo,
97 alphaMode = .Opaque,
98 }
99
100 r.const_buffer = wgpu.DeviceCreateBuffer(r.device, &{
101 label = "Constant buffer",
102 usage = { .Uniform, .CopyDst },
103 size = size_of(matrix[4, 4]f32),
104 })
105
106 r.atlas_texture = wgpu.DeviceCreateTexture(r.device, &{
107 usage = { .TextureBinding, .CopyDst },
108 dimension = ._2D,
109 size = { mu.DEFAULT_ATLAS_WIDTH, mu.DEFAULT_ATLAS_HEIGHT, 1 },
110 format = .R8Unorm,
111 mipLevelCount = 1,
112 sampleCount = 1,
113 })
114 r.atlas_texture_view = wgpu.TextureCreateView(r.atlas_texture, nil)
115
116 r.sampler = wgpu.DeviceCreateSampler(r.device, &{
117 addressModeU = .ClampToEdge,
118 addressModeV = .ClampToEdge,
119 addressModeW = .ClampToEdge,
120 magFilter = .Nearest,
121 minFilter = .Nearest,
122 mipmapFilter = .Nearest,
123 lodMinClamp = 0,
124 lodMaxClamp = 32,
125 compare = .Undefined,
126 maxAnisotropy = 1,
127 })
128
129 r.vertex_buffer = wgpu.DeviceCreateBuffer(r.device, &{
130 label = "Vertex Buffer",
131 usage = { .Vertex, .CopyDst },
132 size = size_of(r.vert_buf),
133 })
134
135 r.tex_buffer = wgpu.DeviceCreateBuffer(r.device, &{
136 label = "Texture Buffer",
137 usage = { .Vertex, .CopyDst },
138 size = size_of(r.tex_buf),
139 })
140
141 r.color_buffer = wgpu.DeviceCreateBuffer(r.device, &{
142 label = "Color Buffer",
143 usage = { .Vertex, .CopyDst },
144 size = size_of(r.color_buf),
145 })
146
147 r.index_buffer = wgpu.DeviceCreateBuffer(r.device, &{
148 label = "Index Buffer",
149 usage = { .Index, .CopyDst },
150 size = size_of(r.index_buf),
151 })
152
153 r.bind_group_layout = wgpu.DeviceCreateBindGroupLayout(r.device, &{
154 entryCount = 3,
155 entries = raw_data([]wgpu.BindGroupLayoutEntry{
156 {
157 binding = 0,
158 visibility = { .Fragment },
159 sampler = {
160 type = .Filtering,
161 },
162 },
163 {
164 binding = 1,
165 visibility = { .Fragment },
166 texture = {
167 sampleType = .Float,
168 viewDimension = ._2D,
169 multisampled = false,
170 },
171 },
172 {
173 binding = 2,
174 visibility = { .Vertex },
175 buffer = {
176 type = .Uniform,
177 minBindingSize = size_of(matrix[4, 4]f32),
178 },
179 },
180 }),
181 })
182
183 r.bind_group = wgpu.DeviceCreateBindGroup(r.device, &{
184 layout = r.bind_group_layout,
185 entryCount = 3,
186 entries = raw_data([]wgpu.BindGroupEntry{
187 {
188 binding = 0,
189 sampler = r.sampler,
190 },
191 {
192 binding = 1,
193 textureView = r.atlas_texture_view,
194 },
195 {
196 binding = 2,
197 buffer = r.const_buffer,
198 size = size_of(matrix[4, 4]f32),
199 },
200 }),
201 })
202
203 r.module = wgpu.DeviceCreateShaderModule(r.device, &{
204 nextInChain = &wgpu.ShaderSourceWGSL{
205 sType = .ShaderSourceWGSL,
206 code = #load("shader.wgsl"),
207 },
208 })
209
210 r.pipeline_layout = wgpu.DeviceCreatePipelineLayout(r.device, &{
211 bindGroupLayoutCount = 1,
212 bindGroupLayouts = &r.bind_group_layout,
213 })
214 r.pipeline = wgpu.DeviceCreateRenderPipeline(r.device, &{
215 layout = r.pipeline_layout,
216 vertex = {
217 module = r.module,
218 entryPoint = "vs_main",
219 bufferCount = 3,
220 buffers = raw_data([]wgpu.VertexBufferLayout{
221 {
222 stepMode = .Vertex,
223 arrayStride = 8,
224 attributeCount = 1,
225 attributes = &wgpu.VertexAttribute{
226 format = .Float32x2,
227 shaderLocation = 0,
228 },
229 },
230 {
231 stepMode = .Vertex,
232 arrayStride = 8,
233 attributeCount = 1,
234 attributes = &wgpu.VertexAttribute{
235 format = .Float32x2,
236 shaderLocation = 1,
237 },
238 },
239 {
240 stepMode = .Vertex,
241 arrayStride = 4,
242 attributeCount = 1,
243 attributes = &wgpu.VertexAttribute{
244 format = .Uint32,
245 shaderLocation = 2,
246 },
247 },
248 }),
249 },
250 fragment = &{
251 module = r.module,
252 entryPoint = "fs_main",
253 targetCount = 1,
254 targets = &wgpu.ColorTargetState{
255 format = .BGRA8Unorm,
256 blend = &{
257 alpha = {
258 srcFactor = .SrcAlpha,
259 dstFactor = .OneMinusSrcAlpha,
260 operation = .Add,
261 },
262 color = {
263 srcFactor = .SrcAlpha,
264 dstFactor = .OneMinusSrcAlpha,
265 operation = .Add,
266 },
267 },
268 writeMask = wgpu.ColorWriteMaskFlags_All,
269 },
270 },
271 primitive = {
272 topology = .TriangleList,
273 cullMode = .None,
274 },
275 multisample = {
276 count = 1,
277 mask = 0xFFFFFFFF,
278 },
279 })
280
281 r.queue = wgpu.DeviceGetQueue(r.device)
282
283 wgpu.QueueWriteTexture(
284 r.queue,
285 &{ texture = r.atlas_texture },
286 &mu.default_atlas_alpha,
287 mu.DEFAULT_ATLAS_WIDTH*mu.DEFAULT_ATLAS_HEIGHT,
288 &{
289 bytesPerRow = mu.DEFAULT_ATLAS_WIDTH,
290 rowsPerImage = mu.DEFAULT_ATLAS_HEIGHT,
291 },
292 &{ mu.DEFAULT_ATLAS_WIDTH, mu.DEFAULT_ATLAS_HEIGHT, 1 },
293 )
294
295 r_write_consts()
296
297 wgpu.SurfaceConfigure(r.surface, &r.config)
298
299 os_run()
300}
301
302r_resize :: proc() {
303 r := &state.renderer
304
305 width, height := os_get_render_bounds()
306 r.config.width, r.config.height = width, height
307 wgpu.SurfaceConfigure(r.surface, &r.config)
308
309 r_write_consts()
310}
311
312r_write_consts :: proc() {
313 r := &state.renderer
314
315
316 dpi := os_get_dpi()
317 width, height := os_get_render_bounds()
318 fw, fh := f32(width), f32(height)
319 transform := linalg.matrix_ortho3d(0, fw, fh, 0, -1, 1) * linalg.matrix4_scale(dpi)
320
321 wgpu.QueueWriteBuffer(r.queue, r.const_buffer, 0, &transform, size_of(transform))
322}
323
324r_clear :: proc(color: mu.Color) -> bool {
325 r := &state.renderer
326
327 r.buf_idx = 0
328 r.prev_buf_idx = 0
329
330 r.curr_texture = wgpu.SurfaceGetCurrentTexture(r.surface)
331 switch r.curr_texture.status {
332 case .SuccessOptimal, .SuccessSuboptimal:
333
334 case .Timeout, .Outdated, .Lost:
335 if r.curr_texture.texture != nil {
336 wgpu.TextureRelease(r.curr_texture.texture)
337 }
338 r_resize()
339 return false
340 case .Occluded:
341
342 return false
343 case .Error:
344 fmt.panicf("get_current_texture status=%v", r.curr_texture.status)
345 }
346
347 r.curr_view = wgpu.TextureCreateView(r.curr_texture.texture, nil)
348
349 r.curr_encoder = wgpu.DeviceCreateCommandEncoder(r.device, nil)
350
351 r.curr_pass = wgpu.CommandEncoderBeginRenderPass(r.curr_encoder, &{
352 colorAttachmentCount = 1,
353 colorAttachments = raw_data([]wgpu.RenderPassColorAttachment{
354 {
355 view = r.curr_view,
356 loadOp = .Clear,
357 storeOp = .Store,
358 clearValue = {f64(color.r)/255, f64(color.g)/255, f64(color.b)/255, f64(color.a)/255},
359 depthSlice = wgpu.DEPTH_SLICE_UNDEFINED,
360 },
361 }),
362 })
363
364 r_bind()
365
366 return true
367}
368
369r_bind :: proc() {
370 r := &state.renderer
371
372 wgpu.RenderPassEncoderSetPipeline(r.curr_pass, r.pipeline)
373 wgpu.RenderPassEncoderSetBindGroup(r.curr_pass, 0, r.bind_group)
374 wgpu.RenderPassEncoderSetVertexBuffer(r.curr_pass, 0, r.vertex_buffer, 0, size_of(r.vert_buf))
375 wgpu.RenderPassEncoderSetVertexBuffer(r.curr_pass, 1, r.tex_buffer, 0, size_of(r.tex_buf))
376 wgpu.RenderPassEncoderSetVertexBuffer(r.curr_pass, 2, r.color_buffer, 0, size_of(r.color_buf))
377 wgpu.RenderPassEncoderSetIndexBuffer(r.curr_pass, r.index_buffer, .Uint32, 0, size_of(r.index_buf))
378}
379
380r_flush :: proc() {
381 r := &state.renderer
382
383 if r.buf_idx == 0 || r.buf_idx == r.prev_buf_idx { return }
384
385 delta := uint(r.buf_idx - r.prev_buf_idx)
386 wgpu.RenderPassEncoderDrawIndexed(r.curr_pass, u32(delta * 6), 1, r.prev_buf_idx*6, 0, 0)
387
388 r.prev_buf_idx = r.buf_idx
389}
390
391r_full_flush :: proc() {
392 r := &state.renderer
393
394 r_submit()
395
396 r.buf_idx = 0
397 r.prev_buf_idx = 0
398
399 r.curr_encoder = wgpu.DeviceCreateCommandEncoder(r.device, nil)
400 r.curr_pass = wgpu.CommandEncoderBeginRenderPass(r.curr_encoder, &{
401 colorAttachmentCount = 1,
402 colorAttachments = &wgpu.RenderPassColorAttachment{
403 view = r.curr_view,
404 loadOp = .Load,
405 storeOp = .Store,
406 },
407 })
408
409 r_bind()
410}
411
412r_submit :: proc() {
413 r := &state.renderer
414
415 r_flush()
416
417 wgpu.QueueWriteBuffer(r.queue, r.vertex_buffer, 0, &r.vert_buf, uint(r.buf_idx*8*size_of(f32)))
418 wgpu.QueueWriteBuffer(r.queue, r.tex_buffer, 0, &r.tex_buf, uint(r.buf_idx*8*size_of(f32)))
419 wgpu.QueueWriteBuffer(r.queue, r.color_buffer, 0, &r.color_buf, uint(r.buf_idx*16))
420 wgpu.QueueWriteBuffer(r.queue, r.index_buffer, 0, &r.index_buf, uint(r.buf_idx*6*size_of(u32)))
421
422 wgpu.RenderPassEncoderEnd(r.curr_pass)
423 wgpu.RenderPassEncoderRelease(r.curr_pass)
424
425 command_buffer := wgpu.CommandEncoderFinish(r.curr_encoder, nil)
426 wgpu.QueueSubmit(r.queue, { command_buffer })
427
428 wgpu.CommandBufferRelease(command_buffer)
429 wgpu.CommandEncoderRelease(r.curr_encoder)
430}
431
432r_present :: proc() {
433 r := &state.renderer
434
435 r_submit()
436
437 wgpu.SurfacePresent(r.surface)
438
439 wgpu.TextureViewRelease(r.curr_view)
440 wgpu.TextureRelease(r.curr_texture.texture)
441}
442
443r_render :: proc() {
444 if !r_clear(state.bg) {
445 return
446 }
447
448 command_backing: ^mu.Command
449 for variant in mu.next_command_iterator(&state.mu_ctx, &command_backing) {
450 switch cmd in variant {
451 case ^mu.Command_Text: r_draw_text(cmd.str, cmd.pos, cmd.color)
452 case ^mu.Command_Rect: r_draw_rect(cmd.rect, cmd.color)
453 case ^mu.Command_Icon: r_draw_icon(cmd.id, cmd.rect, cmd.color)
454 case ^mu.Command_Clip: r_set_clip_rect(cmd.rect)
455 case ^mu.Command_Jump: unreachable()
456 }
457 }
458
459 r_present()
460}
461
462push_quad :: proc(dst, src: mu.Rect, color: mu.Color) #no_bounds_check {
463 r := &state.renderer
464
465 if (r.buf_idx == BUFFER_SIZE) {
466 r_full_flush()
467 }
468
469 textvert_idx := r.buf_idx * 8
470 color_idx := r.buf_idx * 16
471 element_idx := u32(r.buf_idx * 4)
472 index_idx := r.buf_idx * 6
473
474 r.buf_idx += 1
475
476 x := f32(src.x) / mu.DEFAULT_ATLAS_WIDTH
477 y := f32(src.y) / mu.DEFAULT_ATLAS_HEIGHT
478 w := f32(src.w) / mu.DEFAULT_ATLAS_WIDTH
479 h := f32(src.h) / mu.DEFAULT_ATLAS_HEIGHT
480 copy(r.tex_buf[textvert_idx:], []f32{
481 x, y,
482 x + w, y,
483 x, y + h,
484 x + w, y + h,
485 })
486
487 dx, dy, dw, dh := f32(dst.x), f32(dst.y), f32(dst.w), f32(dst.h)
488 copy(r.vert_buf[textvert_idx:], []f32{
489 dx, dy,
490 dx + dw, dy,
491 dx, dy + dh,
492 dx + dw, dy + dh,
493 })
494
495 color := color
496 intr.mem_copy_non_overlapping(raw_data(r.color_buf[color_idx + 0:]), &color, 4)
497 intr.mem_copy_non_overlapping(raw_data(r.color_buf[color_idx + 4:]), &color, 4)
498 intr.mem_copy_non_overlapping(raw_data(r.color_buf[color_idx + 8:]), &color, 4)
499 intr.mem_copy_non_overlapping(raw_data(r.color_buf[color_idx + 12:]), &color, 4)
500
501 copy(r.index_buf[index_idx:], []u32{
502 element_idx + 0,
503 element_idx + 1,
504 element_idx + 2,
505 element_idx + 2,
506 element_idx + 3,
507 element_idx + 1,
508 })
509}
510
511r_draw_rect :: proc(rect: mu.Rect, color: mu.Color) {
512 push_quad(rect, mu.default_atlas[mu.DEFAULT_ATLAS_WHITE], color)
513}
514
515r_draw_text :: proc(text: string, pos: mu.Vec2, color: mu.Color) {
516 dst := mu.Rect{ pos.x, pos.y, 0, 0 }
517 for ch in text {
518 if ch&0xc0 != 0x80 {
519 r := min(int(ch), 127)
520 src := mu.default_atlas[mu.DEFAULT_ATLAS_FONT + r]
521 dst.w = src.w
522 dst.h = src.h
523 push_quad(dst, src, color)
524 dst.x += dst.w
525 }
526 }
527}
528
529r_draw_icon :: proc(id: mu.Icon, rect: mu.Rect, color: mu.Color) {
530 src := mu.default_atlas[id]
531 x := rect.x + (rect.w - src.w) / 2
532 y := rect.y + (rect.h - src.h) / 2
533 push_quad({x, y, src.w, src.h}, src, color)
534}
535
536r_set_clip_rect :: proc(rect: mu.Rect) {
537 r := &state.renderer
538 r_flush()
539
540 x := min(u32(rect.x), r.config.width)
541 y := min(u32(rect.y), r.config.height)
542 w := min(u32(rect.w), r.config.width-x)
543 h := min(u32(rect.h), r.config.height-y)
544 wgpu.RenderPassEncoderSetScissorRect(r.curr_pass, x, y, w, h)
545}