main.odin ¶
1package sdl3_gpu_example
2
3import "core:encoding/json"
4import "core:fmt"
5import "core:math"
6import "core:math/linalg"
7import "core:mem"
8import "core:os"
9import "core:strings"
10import "core:time"
11import "vendor:sdl3"
12
13Mat4 :: matrix[4, 4]f32
14Vec2 :: [2]f32
15Vec4 :: [4]f32
16Quad :: [4]Vec2
17Color :: [4]f32
18
19WHITE :: Color{1, 1, 1, 1}
20BG_COLOR :: Color{0.09, 0.09, 0.11, 1.0}
21
22Draw_Mode :: enum i32 {
23 MSDF = 0,
24 Squircle = 1,
25 Texture = 2,
26}
27
28
29Vertex :: struct {
30 pos: Vec2,
31 uv: Vec2,
32 color: Color,
33}
34
35Vert_Uniforms :: struct {
36 mvp: Mat4,
37}
38
39Frag_Uniforms :: struct {
40 mode: Draw_Mode,
41 screen_px_range: f32,
42 half_size: Vec2,
43 corner_radius: f32,
44 squircle_blend: f32,
45 _pad0: f32,
46 _pad1: f32,
47}
48
49Draw_Call :: struct {
50 texture: ^sdl3.GPUTexture,
51 frag: Frag_Uniforms,
52 index_off: u32,
53 index_count: u32,
54}
55
56Renderer :: struct {
57 device: ^sdl3.GPUDevice,
58 window: ^sdl3.Window,
59 pipeline: ^sdl3.GPUGraphicsPipeline,
60 white_tex: Texture,
61 nearest_sampler: ^sdl3.GPUSampler,
62 linear_sampler: ^sdl3.GPUSampler,
63
64
65 verts: [dynamic]Vertex,
66 idxs: [dynamic]u16,
67 calls: [dynamic]Draw_Call,
68
69
70 vbo: ^sdl3.GPUBuffer,
71 ibo: ^sdl3.GPUBuffer,
72 vbo_cap: int,
73 ibo_cap: int,
74
75 w, h: u32,
76}
77
78Texture :: struct {
79 gpu: ^sdl3.GPUTexture,
80 size: Vec2,
81}
82
83Glyph :: struct {
84 advance: f32,
85 has_bounds: bool,
86 plane: Vec4,
87 uv: Quad,
88}
89
90Font :: struct {
91 texture: Texture,
92 atlas_w, atlas_h: f32,
93 distance_range: f32,
94 atlas_size_px: f32,
95 variants: []Font_Variant,
96}
97
98Font_Variant :: struct {
99 glyphs: map[rune]Glyph,
100 line_height: f32,
101 ascender: f32,
102 descender: f32,
103}
104
105Bounds :: struct {
106 left, bottom, right, top: f32,
107}
108
109
110
111
112
113
114
115
116
117
118
119
120Atlas_JSON :: struct {
121 atlas: struct {
122 distance_range: f32 `json:"distanceRange"`,
123 size: f32,
124 width: f32,
125 height: f32,
126 },
127 variants: []struct {
128 metrics: struct {
129 line_height: f32 `json:"lineHeight"`,
130 ascender: f32,
131 descender: f32,
132 },
133 glyphs: []struct {
134 unicode: int,
135 advance: f32,
136 plane_bounds: Bounds `json:"planeBounds"`,
137 atlas_bounds: Bounds `json:"atlasBounds"`,
138 },
139 },
140}
141
142destroy_renderer :: proc(r: ^Renderer) {
143 delete(r.verts)
144 delete(r.idxs)
145 delete(r.calls)
146}
147
148destroy_font :: proc(font: Font) {
149 for variant in font.variants {
150 delete(variant.glyphs)
151 }
152 delete(font.variants)
153}
154
155read_file_or_die :: proc(path: string, allocator := context.allocator) -> []u8 {
156 data, err := os.read_entire_file(path, allocator)
157 fmt.assertf(err == nil, "Failed to read file: %s", path)
158 return data
159}
160
161load_shader :: proc(r: ^Renderer, path: string, stage: sdl3.GPUShaderStage, num_samplers, num_uniform_buffers: u32) -> ^sdl3.GPUShader {
162 code := read_file_or_die(path)
163 defer delete(code)
164
165 info := sdl3.GPUShaderCreateInfo{
166 code_size = len(code),
167 code = raw_data(code),
168 entrypoint = "main",
169 format = {.SPIRV},
170 stage = stage,
171 num_samplers = num_samplers,
172 num_storage_textures = 0,
173 num_storage_buffers = 0,
174 num_uniform_buffers = num_uniform_buffers,
175 }
176
177 shader := sdl3.CreateGPUShader(r.device, info)
178 fmt.assertf(shader != nil, "CreateGPUShader failed for %s: %s", path, sdl3.GetError())
179 return shader
180}
181
182renderer_init :: proc(window: ^sdl3.Window) -> Renderer {
183 r: Renderer
184 r.window = window
185
186 r.device = sdl3.CreateGPUDevice({.SPIRV}, true, nil)
187 fmt.assertf(r.device != nil, "CreateGPUDevice failed: %s", sdl3.GetError())
188 fmt.assertf(sdl3.ClaimWindowForGPUDevice(r.device, window), "ClaimWindowForGPUDevice failed: %s", sdl3.GetError())
189
190
191
192
193 vert_shader := load_shader(&r, "res/shader.vert.spv", .VERTEX, 0, 1)
194 frag_shader := load_shader(&r, "res/shader.frag.spv", .FRAGMENT, 1, 1)
195
196 vertex_attrs := []sdl3.GPUVertexAttribute{
197 {location = 0, buffer_slot = 0, format = .FLOAT2, offset = u32(offset_of(Vertex, pos))},
198 {location = 1, buffer_slot = 0, format = .FLOAT2, offset = u32(offset_of(Vertex, uv))},
199 {location = 2, buffer_slot = 0, format = .FLOAT4, offset = u32(offset_of(Vertex, color))},
200 }
201
202 vertex_buffers := []sdl3.GPUVertexBufferDescription{
203 {slot = 0, pitch = u32(size_of(Vertex)), input_rate = .VERTEX, instance_step_rate = 0},
204 }
205
206 color_target := sdl3.GPUColorTargetDescription{
207 format = sdl3.GetGPUSwapchainTextureFormat(r.device, window),
208 blend_state = sdl3.GPUColorTargetBlendState{
209 enable_blend = true,
210 src_color_blendfactor = .SRC_ALPHA,
211 dst_color_blendfactor = .ONE_MINUS_SRC_ALPHA,
212 color_blend_op = .ADD,
213 src_alpha_blendfactor = .ONE,
214 dst_alpha_blendfactor = .ONE_MINUS_SRC_ALPHA,
215 alpha_blend_op = .ADD,
216 },
217 }
218
219 pipeline_info := sdl3.GPUGraphicsPipelineCreateInfo{
220 vertex_shader = vert_shader,
221 fragment_shader = frag_shader,
222 vertex_input_state = sdl3.GPUVertexInputState{
223 vertex_buffer_descriptions = raw_data(vertex_buffers),
224 num_vertex_buffers = u32(len(vertex_buffers)),
225 vertex_attributes = raw_data(vertex_attrs),
226 num_vertex_attributes = u32(len(vertex_attrs)),
227 },
228 primitive_type = .TRIANGLELIST,
229 rasterizer_state = sdl3.GPURasterizerState{
230 fill_mode = .FILL,
231 cull_mode = .NONE,
232 },
233 target_info = sdl3.GPUGraphicsPipelineTargetInfo{
234 color_target_descriptions = &color_target,
235 num_color_targets = 1,
236 },
237 }
238
239 r.pipeline = sdl3.CreateGPUGraphicsPipeline(r.device, pipeline_info)
240 fmt.assertf(r.pipeline != nil, "CreateGPUGraphicsPipeline failed: %s", sdl3.GetError())
241
242
243 sdl3.ReleaseGPUShader(r.device, vert_shader)
244 sdl3.ReleaseGPUShader(r.device, frag_shader)
245
246 r.nearest_sampler = sdl3.CreateGPUSampler(r.device, sdl3.GPUSamplerCreateInfo{
247 min_filter = .NEAREST, mag_filter = .NEAREST, mipmap_mode = .NEAREST,
248 address_mode_u = .CLAMP_TO_EDGE, address_mode_v = .CLAMP_TO_EDGE, address_mode_w = .CLAMP_TO_EDGE,
249 })
250 r.linear_sampler = sdl3.CreateGPUSampler(r.device, sdl3.GPUSamplerCreateInfo{
251 min_filter = .LINEAR, mag_filter = .LINEAR, mipmap_mode = .LINEAR,
252 address_mode_u = .CLAMP_TO_EDGE, address_mode_v = .CLAMP_TO_EDGE, address_mode_w = .CLAMP_TO_EDGE,
253 })
254
255 white_pixel := [4]u8{255, 255, 255, 255}
256 r.white_tex = create_texture_from_pixels(&r, raw_data(white_pixel[:]), 1, 1)
257
258 return r
259}
260
261create_texture_from_pixels :: proc(r: ^Renderer, pixels: [^]u8, w, h: i32) -> (res: Texture) {
262 tex_info := sdl3.GPUTextureCreateInfo{
263 type = .D2,
264 format = .R8G8B8A8_UNORM,
265 usage = sdl3.GPUTextureUsageFlags{.SAMPLER},
266 width = u32(w),
267 height = u32(h),
268 layer_count_or_depth = 1,
269 num_levels = 1,
270 }
271 tex := sdl3.CreateGPUTexture(r.device, tex_info)
272
273 byte_size := u32(w * h * 4)
274 transfer := sdl3.CreateGPUTransferBuffer(r.device, sdl3.GPUTransferBufferCreateInfo{
275 usage = .UPLOAD, size = byte_size,
276 })
277
278 mapped := ([^]u8)(sdl3.MapGPUTransferBuffer(r.device, transfer, false))
279 copy(mapped[:byte_size], pixels[:byte_size])
280 sdl3.UnmapGPUTransferBuffer(r.device, transfer)
281
282 cmd := sdl3.AcquireGPUCommandBuffer(r.device)
283 copy_pass := sdl3.BeginGPUCopyPass(cmd)
284
285 src := sdl3.GPUTextureTransferInfo{transfer_buffer = transfer, offset = 0}
286 dst := sdl3.GPUTextureRegion{texture = tex, w = u32(w), h = u32(h), d = 1}
287 sdl3.UploadToGPUTexture(copy_pass, src, dst, false)
288
289 sdl3.EndGPUCopyPass(copy_pass)
290 assert(sdl3.SubmitGPUCommandBuffer(cmd))
291
292 sdl3.ReleaseGPUTransferBuffer(r.device, transfer)
293 return Texture{gpu = tex, size = {f32(w), f32(h)}}
294}
295
296load_texture :: proc(r: ^Renderer, path: string) -> (res: Texture) {
297 cpath := strings.clone_to_cstring(path)
298 defer delete(cpath)
299
300 surface := sdl3.LoadPNG(cpath)
301 fmt.assertf(r.pipeline != nil, "failed to load image %s: %s", path, sdl3.GetError())
302 defer sdl3.DestroySurface(surface)
303
304 converted := sdl3.ConvertSurface(surface, .ABGR8888)
305 defer sdl3.DestroySurface(converted)
306
307 pixels := ([^]u8)(converted.pixels)
308 return create_texture_from_pixels(r, pixels, converted.w, converted.h)
309}
310
311destroy_texture :: proc(r: ^Renderer, tex: Texture) {
312 ensure(r != nil)
313 if tex.gpu != nil {
314 return
315 }
316 sdl3.ReleaseGPUTexture(r.device, tex.gpu)
317}
318
319renderer_begin_frame :: proc(r: ^Renderer) {
320 clear(&r.verts)
321 clear(&r.idxs)
322 clear(&r.calls)
323}
324
325push_call :: proc(r: ^Renderer, tex: ^sdl3.GPUTexture, frag: Frag_Uniforms, index_count: int) {
326 n := len(r.calls)
327 if n > 0 {
328 last := &r.calls[n - 1]
329 if last.texture == tex && last.frag == frag {
330 last.index_count += u32(index_count)
331 return
332 }
333 }
334 append(&r.calls, Draw_Call{
335 texture = tex,
336 frag = frag,
337 index_off = u32(len(r.idxs) - index_count),
338 index_count = u32(index_count),
339 })
340}
341
342make_quad :: proc(pos, size: Vec2) -> (q: Quad) {
343 return {pos, pos + Vec2{size.x, 0}, pos + size, pos + Vec2{0, size.y}}
344}
345
346push_quad_verts :: proc(r: ^Renderer, pos: Quad, uv: [4]Vec2, color: Color) {
347 base := u16(len(r.verts))
348 append(&r.verts, Vertex{pos[0], uv[0], color})
349 append(&r.verts, Vertex{pos[1], uv[1], color})
350 append(&r.verts, Vertex{pos[2], uv[2], color})
351 append(&r.verts, Vertex{pos[3], uv[3], color})
352 append(&r.idxs, base + 0, base + 1, base + 2)
353 append(&r.idxs, base + 0, base + 2, base + 3)
354}
355
356draw_squircle :: proc(r: ^Renderer, pos, size: Vec2, corner_radius: f32, blend: f32, color: Color = WHITE) {
357 half := size * 0.5
358 quad := make_quad(pos, size)
359 uv := make_quad(-half, size)
360 push_quad_verts(r, quad, uv, color)
361 push_call(r, r.white_tex.gpu, Frag_Uniforms{
362 mode = .Squircle,
363 half_size = half,
364 corner_radius = clamp(corner_radius, 0, min(half.x, half.y)),
365 squircle_blend = clamp(blend, 0, 1),
366 }, 6)
367}
368
369draw_rect :: proc(r: ^Renderer, pos, size: Vec2, color: Color) {
370 draw_squircle(r, pos, size, 0, 0, color)
371}
372
373draw_texture :: proc(r: ^Renderer, tex: Texture, pos, size: Vec2, color: Color = WHITE, uv0: Vec2 = {0, 0}, uv1: Vec2 = {1, 1}) {
374 ensure(r != nil)
375 if tex.gpu == nil {
376
377 draw_rect(r, pos, size, {1, 0, 1, 0.5})
378 return
379 }
380 quad := make_quad(pos, size)
381 uv := make_quad(uv0, uv1)
382 push_quad_verts(r, quad, uv, color)
383 push_call(r, tex.gpu, Frag_Uniforms{mode = .Texture}, 6)
384}
385
386load_font :: proc(r: ^Renderer, png_path, json_path: string) -> (res: Font) {
387 res.texture = load_texture(r, png_path)
388
389 data := read_file_or_die(json_path)
390 defer delete(data)
391
392 parsed: Atlas_JSON
393 err := json.unmarshal(data, &parsed)
394 fmt.assertf(err == nil, "Failed to parse font JSON %s: %v", json_path, err)
395
396 res.atlas_w = parsed.atlas.width
397 res.atlas_h = parsed.atlas.height
398 res.distance_range = parsed.atlas.distance_range
399 res.atlas_size_px = parsed.atlas.size
400
401 fmt.assertf(len(parsed.variants) > 0, "Font JSONs %s has %v variants", json_path, len(parsed.variants))
402
403 res.variants = make([]Font_Variant, len(parsed.variants))
404
405 for &variant, i in res.variants {
406 parsed_variant := parsed.variants[i]
407 variant.line_height = parsed_variant.metrics.line_height
408 variant.ascender = parsed_variant.metrics.ascender
409 variant.descender = parsed_variant.metrics.descender
410 variant.glyphs = make(map[rune]Glyph)
411
412 for g in parsed_variant.glyphs {
413 glyph: Glyph
414 glyph.advance = g.advance
415 if g.plane_bounds != {} {
416 glyph.plane = transmute(Vec4)g.plane_bounds
417 glyph.has_bounds = true
418 }
419 if g.atlas_bounds != {} {
420 atlas := transmute(Vec4)g.atlas_bounds
421
422 u0 := atlas.x / res.atlas_w
423 v0 := 1.0 - (atlas.w / res.atlas_h)
424 u1 := atlas.z / res.atlas_w
425 v1 := 1.0 - (atlas.y / res.atlas_h)
426
427 glyph.uv = {{u0, v0}, {u1, v0}, {u1, v1}, {u0, v1}}
428 }
429 variant.glyphs[rune(g.unicode)] = glyph
430 }
431 }
432
433 for v in parsed.variants {
434 delete(v.glyphs)
435 }
436 delete(parsed.variants)
437
438 return res
439}
440
441measure :: proc(font: ^Font, face_id: int, text: string, font_size: f32) -> (res: Vec2) {
442 variant := font.variants[face_id]
443 line_count := f32(1)
444 width := f32(0)
445
446 for ch in text {
447 if ch == '\n' {
448 line_count += 1
449 res.x = max(res.x, width)
450 width = 0
451 continue
452 }
453
454 glyph, _ := variant.glyphs[ch]
455 width += glyph.advance * font_size
456 }
457
458 res.x = width
459 res.y = variant.line_height * font_size * line_count
460
461 return
462}
463
464draw_text :: proc(r: ^Renderer, font: ^Font, face_id: int, text: string, pos: Vec2, font_size: f32, color: Color = WHITE) {
465 cursor := Vec2{pos.x, pos.y + font_size}
466 screen_px_range := font.distance_range * (font_size / font.atlas_size_px)
467 variant := font.variants[face_id]
468
469 start_index_count := 0
470
471 for ch in text {
472 if ch == '\n' {
473 cursor.x = pos.x
474 cursor.y += variant.line_height * font_size
475 continue
476 }
477
478 glyph, found := variant.glyphs[ch]
479 if !found || !glyph.has_bounds {
480 if found {
481 cursor.x += glyph.advance * font_size
482 }
483 continue
484 }
485
486 x0 := cursor.x + glyph.plane.x * font_size
487 y0 := cursor.y - glyph.plane.w * font_size
488 x1 := cursor.x + glyph.plane.z * font_size
489 y1 := cursor.y - glyph.plane.y * font_size
490
491 push_quad_verts(r,
492 {{x0, y0}, {x1, y0}, {x1, y1}, {x0, y1}},
493 glyph.uv,
494 color)
495
496 start_index_count += 6
497 cursor.x += glyph.advance * font_size
498 }
499
500 if start_index_count > 0 {
501 push_call(r, font.texture.gpu, Frag_Uniforms{
502 mode = .MSDF,
503 screen_px_range = screen_px_range,
504 }, start_index_count)
505 }
506}
507
508ensure_buffer_capacity :: proc(r: ^Renderer, buf: ^^sdl3.GPUBuffer, cap: ^int, needed_bytes: int, usage: sdl3.GPUBufferUsageFlags) {
509 if needed_bytes <= cap^ {
510 return
511 }
512 if buf^ != nil {
513 sdl3.ReleaseGPUBuffer(r.device, buf^)
514 }
515 new_cap := max(needed_bytes, cap^ * 2, 4096)
516 buf^ = sdl3.CreateGPUBuffer(r.device, sdl3.GPUBufferCreateInfo{usage = usage, size = u32(new_cap)})
517 cap^ = new_cap
518}
519
520upload_buffer :: proc(r: ^Renderer, dst: ^sdl3.GPUBuffer, data: []$T) {
521 if len(data) == 0 {
522 return
523 }
524 size := len(data) * size_of(T)
525
526 transfer := sdl3.CreateGPUTransferBuffer(r.device, sdl3.GPUTransferBufferCreateInfo{usage = .UPLOAD, size = u32(size)})
527 mapped := ([^]T)(sdl3.MapGPUTransferBuffer(r.device, transfer, false))
528 copy(mapped[:len(data)], data[:])
529 sdl3.UnmapGPUTransferBuffer(r.device, transfer)
530
531 cmd := sdl3.AcquireGPUCommandBuffer(r.device)
532 copy_pass := sdl3.BeginGPUCopyPass(cmd)
533 src := sdl3.GPUTransferBufferLocation{transfer_buffer = transfer, offset = 0}
534 dst_region := sdl3.GPUBufferRegion{buffer = dst, offset = 0, size = u32(size)}
535 sdl3.UploadToGPUBuffer(copy_pass, src, dst_region, false)
536 sdl3.EndGPUCopyPass(copy_pass)
537 assert(sdl3.SubmitGPUCommandBuffer(cmd))
538
539 sdl3.ReleaseGPUTransferBuffer(r.device, transfer)
540}
541
542
543renderer_flush :: proc(r: ^Renderer) {
544 vbytes := len(r.verts) * size_of(Vertex)
545 ibytes := len(r.idxs) * size_of(u16)
546
547 ensure_buffer_capacity(r, &r.vbo, &r.vbo_cap, vbytes, sdl3.GPUBufferUsageFlags{.VERTEX})
548 ensure_buffer_capacity(r, &r.ibo, &r.ibo_cap, ibytes, sdl3.GPUBufferUsageFlags{.INDEX})
549
550 if len(r.verts) > 0 {
551 upload_buffer(r, r.vbo, r.verts[:])
552 }
553 if len(r.idxs) > 0 {
554 upload_buffer(r, r.ibo, r.idxs[:])
555 }
556
557 cmd := sdl3.AcquireGPUCommandBuffer(r.device)
558
559 swap_tex: ^sdl3.GPUTexture
560 if !sdl3.WaitAndAcquireGPUSwapchainTexture(cmd, r.window, &swap_tex, &r.w, &r.h) {
561 fmt.eprintfln("WaitAndAcquireGPUSwapchainTexture failed: %s", sdl3.GetError())
562 assert(sdl3.SubmitGPUCommandBuffer(cmd))
563 return
564 }
565 if swap_tex == nil {
566 assert(sdl3.SubmitGPUCommandBuffer(cmd))
567 return
568 }
569
570 color_target := sdl3.GPUColorTargetInfo{
571 texture = swap_tex,
572 load_op = .CLEAR,
573 clear_color = sdl3.FColor(BG_COLOR),
574 store_op = .STORE,
575 }
576
577 pass := sdl3.BeginGPURenderPass(cmd, &color_target, 1, nil)
578 sdl3.BindGPUGraphicsPipeline(pass, r.pipeline)
579
580 vbuf_binding := sdl3.GPUBufferBinding{buffer = r.vbo, offset = 0}
581 sdl3.BindGPUVertexBuffers(pass, 0, &vbuf_binding, 1)
582 ibuf_binding := sdl3.GPUBufferBinding{buffer = r.ibo, offset = 0}
583 sdl3.BindGPUIndexBuffer(pass, ibuf_binding, ._16BIT)
584
585 vert_u := Vert_Uniforms{mvp = linalg.matrix_ortho3d(0, f32(r.w), f32(r.h), 0, -1, 1)}
586 sdl3.PushGPUVertexUniformData(cmd, 0, &vert_u, size_of(vert_u))
587
588 for &call in r.calls {
589 sampler := r.nearest_sampler if call.frag.mode == .Squircle else r.linear_sampler
590 binding := sdl3.GPUTextureSamplerBinding{texture = call.texture, sampler = sampler}
591 sdl3.BindGPUFragmentSamplers(pass, 0, &binding, 1)
592
593 sdl3.PushGPUFragmentUniformData(cmd, 0, &call.frag, size_of(Frag_Uniforms))
594 sdl3.DrawGPUIndexedPrimitives(pass, call.index_count, 1, call.index_off, 0, 0)
595 }
596
597 sdl3.EndGPURenderPass(pass)
598 assert(sdl3.SubmitGPUCommandBuffer(cmd))
599}
600
601_main :: proc() {
602 fmt.assertf(sdl3.Init(sdl3.InitFlags{.VIDEO}), "SDL_Init failed: %s", sdl3.GetError())
603 defer sdl3.Quit()
604
605 window := sdl3.CreateWindow("SDL3 GPU - Odin", 1280, 720, sdl3.WindowFlags{.RESIZABLE})
606 fmt.assertf(window != nil, "CreateWindow failed: %s", sdl3.GetError())
607 defer sdl3.DestroyWindow(window)
608
609 r := renderer_init(window)
610 defer {
611 destroy_texture(&r, r.white_tex)
612 destroy_renderer(&r)
613 }
614
615 photo := load_texture(&r, "res/emblem.png")
616 defer destroy_texture(&r, photo)
617
618 font := load_font(&r, "res/font.png", "res/font.json")
619 defer destroy_font(font)
620
621 running := true
622 minimized := .MINIMIZED in sdl3.GetWindowFlags(window)
623
624 handle_event :: proc(event: ^sdl3.Event, running, minimized: ^bool) {
625 #partial switch event.type {
626 case .QUIT:
627 running^ = false
628 case .KEY_DOWN:
629 if event.key.key == sdl3.K_ESCAPE {
630 running^ = false
631 }
632 case .WINDOW_MINIMIZED:
633 minimized^ = true
634 case .WINDOW_RESTORED:
635 minimized^ = false
636 }
637 }
638
639 dt: time.Duration
640 face_id: int
641 angle: f32
642
643 for running {
644 if minimized {
645
646
647 event: sdl3.Event
648 if sdl3.WaitEventTimeout(&event, 100) {
649 handle_event(&event, &running, &minimized)
650 }
651 continue
652 }
653
654 start := time.tick_now()
655 defer dt = time.tick_since(start)
656
657 event: sdl3.Event
658 for sdl3.PollEvent(&event) {
659 handle_event(&event, &running, &minimized)
660 }
661
662 renderer_begin_frame(&r)
663
664 if angle += (f32(dt) / 1e9 * math.PI); angle > math.TAU {
665 angle = math.mod(angle, math.TAU)
666 face_id += 1
667 if face_id >= len(font.variants) {
668 face_id = 0
669 }
670 }
671
672 size := Vec2{160, 160}
673 pos := Vec2{1280 / 2 - size.x / 2, size.y + 60}
674 radius := f32(24)
675
676 draw_rect(&r, pos - {0, 180}, size, {0.95, 0.25, 0.95, 1.0})
677 draw_squircle(&r, pos - {180, 0}, size, radius, 1.0, {0.95, 0.25, 0.25, 1.0})
678 draw_squircle(&r, pos, size, radius, 0.5, {0.25, 0.95, 0.25, 1.0})
679 draw_texture(&r, photo, pos + {180, 0}, size)
680
681 radius += math.sin(angle) * 12
682 factor := f32(0.5) + math.sin(angle) * 0.5
683
684 draw_squircle(&r, pos + {0, size.y + 20}, size, radius, factor, {0.95, 0.95, 0.25, 1.0})
685
686 font_size := f32(64) + math.sin(angle) * 32
687 extents := measure(&font, face_id, "Hellope, World!", font_size)
688 draw_text(&r, &font, face_id, "Hellope, World!", {1280 / 2, 650} - extents / 2, font_size, WHITE)
689
690 renderer_flush(&r)
691 }
692}
693
694main :: proc() {
695 track: mem.Tracking_Allocator
696 mem.tracking_allocator_init(&track, context.allocator)
697 defer mem.tracking_allocator_destroy(&track)
698 context.allocator = mem.tracking_allocator(&track)
699
700 _main()
701
702 for _, leak in track.allocation_map {
703 fmt.eprintfln("%v leaked %m", leak.location, leak.size)
704 }
705}