diff --git a/draw/draw.odin b/draw/draw.odin index c368cde..457a938 100644 --- a/draw/draw.odin +++ b/draw/draw.odin @@ -58,8 +58,8 @@ Rectangle :: struct { Sub_Batch_Kind :: enum u8 { Shapes, // non-indexed, white texture, mode 0 - Text, // indexed, atlas texture, mode 0 - SDF, // instanced unit quad, white texture, mode 1 + Text, // indexed, atlas texture, mode 0 + SDF, // instanced unit quad, white texture, mode 1 } Sub_Batch :: struct { @@ -89,34 +89,34 @@ Scissor :: struct { GLOB: Global Global :: struct { - odin_context: runtime.Context, - pipeline_2d_base: Pipeline_2D_Base, - text_cache: Text_Cache, - layers: [dynamic]Layer, - scissors: [dynamic]Scissor, - tmp_shape_verts: [dynamic]Vertex, - tmp_text_verts: [dynamic]Vertex, - tmp_text_indices: [dynamic]c.int, - tmp_text_batches: [dynamic]TextBatch, - tmp_primitives: [dynamic]Primitive, - tmp_sub_batches: [dynamic]Sub_Batch, - clay_mem: [^]u8, - msaa_texture: ^sdl.GPUTexture, - curr_layer_index: uint, - max_layers: int, - max_scissors: int, - max_shape_verts: int, - max_text_verts: int, - max_text_indices: int, - max_text_batches: int, - max_primitives: int, - max_sub_batches: int, - dpi_scaling: f32, - msaa_w: u32, - msaa_h: u32, - sample_count: sdl.GPUSampleCount, - clay_z_index: i16, - cleared: bool, + odin_context: runtime.Context, + pipeline_2d_base: Pipeline_2D_Base, + text_cache: Text_Cache, + layers: [dynamic]Layer, + scissors: [dynamic]Scissor, + tmp_shape_verts: [dynamic]Vertex, + tmp_text_verts: [dynamic]Vertex, + tmp_text_indices: [dynamic]c.int, + tmp_text_batches: [dynamic]TextBatch, + tmp_primitives: [dynamic]Primitive, + tmp_sub_batches: [dynamic]Sub_Batch, + clay_mem: [^]u8, + msaa_texture: ^sdl.GPUTexture, + curr_layer_index: uint, + max_layers: int, + max_scissors: int, + max_shape_verts: int, + max_text_verts: int, + max_text_indices: int, + max_text_batches: int, + max_primitives: int, + max_sub_batches: int, + dpi_scaling: f32, + msaa_w: u32, + msaa_h: u32, + sample_count: sdl.GPUSampleCount, + clay_z_index: i16, + cleared: bool, } Init_Options :: struct { @@ -186,8 +186,8 @@ init :: proc( return true } -// TODO every x frames nuke max values in case of edge cases where max gets set very high -// Called at the end of every frame +// TODO Either every x frames nuke max values in case of edge cases where max gets set very high +// or leave to application code to decide the right time for resize resize_global :: proc() { if len(GLOB.layers) > GLOB.max_layers do GLOB.max_layers = len(GLOB.layers) shrink(&GLOB.layers, GLOB.max_layers) @@ -546,11 +546,7 @@ prepare_clay_batch :: proc( } // Render primitives. clear_color is the background fill before any layers are drawn. -end :: proc( - device: ^sdl.GPUDevice, - window: ^sdl.Window, - clear_color: Color = BLACK, -) { +end :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window, clear_color: Color = BLACK) { cmd_buffer := sdl.AcquireGPUCommandBuffer(device) if cmd_buffer == nil { log.panicf("Failed to acquire GPU command buffer: %s", sdl.GetError()) @@ -561,10 +557,6 @@ end :: proc( upload(device, copy_pass) sdl.EndGPUCopyPass(copy_pass) - // Resize dynamic arrays - // TODO: This should only be called occasionally, not every frame. - resize_global() - swapchain_texture: ^sdl.GPUTexture w, h: u32 if !sdl.WaitAndAcquireGPUSwapchainTexture(cmd_buffer, window, &swapchain_texture, &w, &h) { diff --git a/draw/pipeline_2d_base.odin b/draw/pipeline_2d_base.odin index 8315c89..2f95fe7 100644 --- a/draw/pipeline_2d_base.odin +++ b/draw/pipeline_2d_base.odin @@ -100,11 +100,11 @@ Shape_Params :: struct #raw_union { // GPU layout: 64 bytes, std430-compatible. The shader declares this as a storage buffer struct. Primitive :: struct { - bounds: [4]f32, // 0: min_x, min_y, max_x, max_y (world-space, pre-DPI) - color: Color, // 16: u8x4, unpacked in shader via unpackUnorm4x8 - kind_flags: u32, // 20: (kind as u32) | (flags as u32 << 8) - _pad: [2]f32, // 24: alignment to vec4 boundary - params: Shape_Params, // 32: two vec4s of shape params + bounds: [4]f32, // 0: min_x, min_y, max_x, max_y (world-space, pre-DPI) + color: Color, // 16: u8x4, unpacked in shader via unpackUnorm4x8 + kind_flags: u32, // 20: (kind as u32) | (flags as u32 << 8) + _pad: [2]f32, // 24: alignment to vec4 boundary + params: Shape_Params, // 32: two vec4s of shape params } #assert(size_of(Primitive) == 64) @@ -279,14 +279,14 @@ create_pipeline_2d_base :: proc( pipeline.white_texture = sdl.CreateGPUTexture( device, sdl.GPUTextureCreateInfo { - type = .D2, - format = .R8G8B8A8_UNORM, - usage = {.SAMPLER}, - width = 1, - height = 1, + type = .D2, + format = .R8G8B8A8_UNORM, + usage = {.SAMPLER}, + width = 1, + height = 1, layer_count_or_depth = 1, - num_levels = 1, - sample_count = ._1, + num_levels = 1, + sample_count = ._1, }, ) if pipeline.white_texture == nil { @@ -314,9 +314,13 @@ create_pipeline_2d_base :: proc( mem.copy(white_ptr, &white_pixel, size_of(white_pixel)) sdl.UnmapGPUTransferBuffer(device, white_transfer) - quad_verts := [6]Vertex{ - {position = {0, 0}}, {position = {1, 0}}, {position = {0, 1}}, - {position = {0, 1}}, {position = {1, 0}}, {position = {1, 1}}, + quad_verts := [6]Vertex { + {position = {0, 0}}, + {position = {1, 0}}, + {position = {0, 1}}, + {position = {0, 1}}, + {position = {1, 0}}, + {position = {1, 1}}, } quad_transfer := sdl.CreateGPUTransferBuffer( device, @@ -353,11 +357,7 @@ create_pipeline_2d_base :: proc( sdl.UploadToGPUBuffer( upload_pass, sdl.GPUTransferBufferLocation{transfer_buffer = quad_transfer}, - sdl.GPUBufferRegion{ - buffer = pipeline.unit_quad_buffer, - offset = 0, - size = size_of(quad_verts), - }, + sdl.GPUBufferRegion{buffer = pipeline.unit_quad_buffer, offset = 0, size = size_of(quad_verts)}, false, ) @@ -373,9 +373,9 @@ create_pipeline_2d_base :: proc( pipeline.sampler = sdl.CreateGPUSampler( device, sdl.GPUSamplerCreateInfo { - min_filter = .LINEAR, - mag_filter = .LINEAR, - mipmap_mode = .LINEAR, + min_filter = .LINEAR, + mag_filter = .LINEAR, + mipmap_mode = .LINEAR, address_mode_u = .CLAMP_TO_EDGE, address_mode_v = .CLAMP_TO_EDGE, address_mode_w = .CLAMP_TO_EDGE, @@ -428,11 +428,7 @@ upload :: proc(device: ^sdl.GPUDevice, pass: ^sdl.GPUCopyPass) { sdl.UploadToGPUBuffer( pass, sdl.GPUTransferBufferLocation{transfer_buffer = GLOB.pipeline_2d_base.vertex_buffer.transfer}, - sdl.GPUBufferRegion{ - buffer = GLOB.pipeline_2d_base.vertex_buffer.gpu, - offset = 0, - size = total_vert_size, - }, + sdl.GPUBufferRegion{buffer = GLOB.pipeline_2d_base.vertex_buffer.gpu, offset = 0, size = total_vert_size}, false, ) } @@ -459,11 +455,7 @@ upload :: proc(device: ^sdl.GPUDevice, pass: ^sdl.GPUCopyPass) { sdl.UploadToGPUBuffer( pass, sdl.GPUTransferBufferLocation{transfer_buffer = GLOB.pipeline_2d_base.index_buffer.transfer}, - sdl.GPUBufferRegion{ - buffer = GLOB.pipeline_2d_base.index_buffer.gpu, - offset = 0, - size = index_size, - }, + sdl.GPUBufferRegion{buffer = GLOB.pipeline_2d_base.index_buffer.gpu, offset = 0, size = index_size}, false, ) } @@ -480,9 +472,7 @@ upload :: proc(device: ^sdl.GPUDevice, pass: ^sdl.GPUCopyPass) { sdl.GPUBufferUsageFlags{.GRAPHICS_STORAGE_READ}, ) - p_array := sdl.MapGPUTransferBuffer( - device, GLOB.pipeline_2d_base.primitive_buffer.transfer, false, - ) + p_array := sdl.MapGPUTransferBuffer(device, GLOB.pipeline_2d_base.primitive_buffer.transfer, false) if p_array == nil { log.panicf("Failed to map primitive transfer buffer: %s", sdl.GetError()) } @@ -491,14 +481,8 @@ upload :: proc(device: ^sdl.GPUDevice, pass: ^sdl.GPUCopyPass) { sdl.UploadToGPUBuffer( pass, - sdl.GPUTransferBufferLocation{ - transfer_buffer = GLOB.pipeline_2d_base.primitive_buffer.transfer, - }, - sdl.GPUBufferRegion{ - buffer = GLOB.pipeline_2d_base.primitive_buffer.gpu, - offset = 0, - size = prim_size, - }, + sdl.GPUTransferBufferLocation{transfer_buffer = GLOB.pipeline_2d_base.primitive_buffer.transfer}, + sdl.GPUBufferRegion{buffer = GLOB.pipeline_2d_base.primitive_buffer.gpu, offset = 0, size = prim_size}, false, ) } @@ -521,10 +505,8 @@ draw_layer :: proc( cmd_buffer, &sdl.GPUColorTargetInfo { texture = render_texture, - clear_color = sdl.FColor { - clear_color[0], clear_color[1], clear_color[2], clear_color[3], - }, - load_op = .CLEAR, + clear_color = sdl.FColor{clear_color[0], clear_color[1], clear_color[2], clear_color[3]}, + load_op = .CLEAR, store_op = .STORE, }, 1, @@ -540,10 +522,8 @@ draw_layer :: proc( cmd_buffer, &sdl.GPUColorTargetInfo { texture = render_texture, - clear_color = sdl.FColor { - clear_color[0], clear_color[1], clear_color[2], clear_color[3], - }, - load_op = GLOB.cleared ? .LOAD : .CLEAR, + clear_color = sdl.FColor{clear_color[0], clear_color[1], clear_color[2], clear_color[3]}, + load_op = GLOB.cleared ? .LOAD : .CLEAR, store_op = .STORE, }, 1, @@ -571,16 +551,14 @@ draw_layer :: proc( // Shorthand aliases for frequently-used pipeline resources main_vbuf := GLOB.pipeline_2d_base.vertex_buffer.gpu unit_quad := GLOB.pipeline_2d_base.unit_quad_buffer - white := GLOB.pipeline_2d_base.white_texture - sampler := GLOB.pipeline_2d_base.sampler - w := f32(swapchain_w) - h := f32(swapchain_h) + white := GLOB.pipeline_2d_base.white_texture + sampler := GLOB.pipeline_2d_base.sampler + w := f32(swapchain_w) + h := f32(swapchain_h) // Initial GPU state: tessellated mode, main vertex buffer, no atlas bound yet push_globals(cmd_buffer, w, h, .Tessellated) - sdl.BindGPUVertexBuffers( - render_pass, 0, &sdl.GPUBufferBinding{buffer = main_vbuf, offset = 0}, 1, - ) + sdl.BindGPUVertexBuffers(render_pass, 0, &sdl.GPUBufferBinding{buffer = main_vbuf, offset = 0}, 1) current_mode: Draw_Mode = .Tessellated current_vbuf := main_vbuf @@ -600,16 +578,15 @@ draw_layer :: proc( current_mode = .Tessellated } if current_vbuf != main_vbuf { - sdl.BindGPUVertexBuffers( - render_pass, 0, - &sdl.GPUBufferBinding{buffer = main_vbuf, offset = 0}, 1, - ) + sdl.BindGPUVertexBuffers(render_pass, 0, &sdl.GPUBufferBinding{buffer = main_vbuf, offset = 0}, 1) current_vbuf = main_vbuf } if current_atlas != white { sdl.BindGPUFragmentSamplers( - render_pass, 0, - &sdl.GPUTextureSamplerBinding{texture = white, sampler = sampler}, 1, + render_pass, + 0, + &sdl.GPUTextureSamplerBinding{texture = white, sampler = sampler}, + 1, ) current_atlas = white } @@ -621,20 +598,15 @@ draw_layer :: proc( current_mode = .Tessellated } if current_vbuf != main_vbuf { - sdl.BindGPUVertexBuffers( - render_pass, 0, - &sdl.GPUBufferBinding{buffer = main_vbuf, offset = 0}, 1, - ) + sdl.BindGPUVertexBuffers(render_pass, 0, &sdl.GPUBufferBinding{buffer = main_vbuf, offset = 0}, 1) current_vbuf = main_vbuf } chunk := &GLOB.tmp_text_batches[batch.offset] if current_atlas != chunk.atlas_texture { sdl.BindGPUFragmentSamplers( - render_pass, 0, - &sdl.GPUTextureSamplerBinding { - texture = chunk.atlas_texture, - sampler = sampler, - }, + render_pass, + 0, + &sdl.GPUTextureSamplerBinding{texture = chunk.atlas_texture, sampler = sampler}, 1, ) current_atlas = chunk.atlas_texture @@ -654,16 +626,15 @@ draw_layer :: proc( current_mode = .SDF } if current_vbuf != unit_quad { - sdl.BindGPUVertexBuffers( - render_pass, 0, - &sdl.GPUBufferBinding{buffer = unit_quad, offset = 0}, 1, - ) + sdl.BindGPUVertexBuffers(render_pass, 0, &sdl.GPUBufferBinding{buffer = unit_quad, offset = 0}, 1) current_vbuf = unit_quad } if current_atlas != white { sdl.BindGPUFragmentSamplers( - render_pass, 0, - &sdl.GPUTextureSamplerBinding{texture = white, sampler = sampler}, 1, + render_pass, + 0, + &sdl.GPUTextureSamplerBinding{texture = white, sampler = sampler}, + 1, ) current_atlas = white } diff --git a/draw/shapes.odin b/draw/shapes.odin index 96c81a8..0d75d74 100644 --- a/draw/shapes.odin +++ b/draw/shapes.odin @@ -324,13 +324,7 @@ triangle_strip :: proc( // ----- SDF drawing functions ---- // Draw a rectangle with per-corner rounding radii via SDF. -rectangle_corners :: proc( - layer: ^Layer, - rect: Rectangle, - radii: [4]f32, - color: Color, - soft_px: f32 = 1.0, -) { +rectangle_corners :: proc(layer: ^Layer, rect: Rectangle, radii: [4]f32, color: Color, soft_px: f32 = 1.0) { max_radius := min(rect.w, rect.h) * 0.5 tl := clamp(radii[0], 0, max_radius) tr := clamp(radii[1], 0, max_radius) @@ -387,13 +381,7 @@ rectangle_corners_lines :: proc( } // Draw a rectangle with uniform corner rounding via SDF. -rectangle_rounded :: proc( - layer: ^Layer, - rect: Rectangle, - roundness: f32, - color: Color, - soft_px: f32 = 1.0, -) { +rectangle_rounded :: proc(layer: ^Layer, rect: Rectangle, roundness: f32, color: Color, soft_px: f32 = 1.0) { cr := min(rect.w, rect.h) * clamp(roundness, 0, 1) * 0.5 if cr < 1 { rectangle(layer, rect, color) @@ -425,12 +413,19 @@ circle :: proc(layer: ^Layer, center: [2]f32, radius: f32, color: Color, soft_px dpi := GLOB.dpi_scaling prim := Primitive { - bounds = {center.x - radius - pad, center.y - radius - pad, - center.x + radius + pad, center.y + radius + pad}, + bounds = { + center.x - radius - pad, + center.y - radius - pad, + center.x + radius + pad, + center.y + radius + pad, + }, color = color, kind_flags = pack_kind_flags(.Circle, {}), } - prim.params.circle = Circle_Params{radius = radius * dpi, soft_px = soft_px} + prim.params.circle = Circle_Params { + radius = radius * dpi, + soft_px = soft_px, + } prepare_sdf_primitive(layer, prim) } @@ -447,35 +442,42 @@ circle_lines :: proc( dpi := GLOB.dpi_scaling prim := Primitive { - bounds = {center.x - radius - pad, center.y - radius - pad, - center.x + radius + pad, center.y + radius + pad}, + bounds = { + center.x - radius - pad, + center.y - radius - pad, + center.x + radius + pad, + center.y + radius + pad, + }, color = color, kind_flags = pack_kind_flags(.Circle, {.Stroke}), } - prim.params.circle = Circle_Params{ - radius = radius * dpi, soft_px = soft_px, stroke_px = thick * dpi, + prim.params.circle = Circle_Params { + radius = radius * dpi, + soft_px = soft_px, + stroke_px = thick * dpi, } prepare_sdf_primitive(layer, prim) } // Draw a filled ellipse via SDF. -ellipse :: proc( - layer: ^Layer, - center: [2]f32, - radius_h, radius_v: f32, - color: Color, - soft_px: f32 = 1.0, -) { +ellipse :: proc(layer: ^Layer, center: [2]f32, radius_h, radius_v: f32, color: Color, soft_px: f32 = 1.0) { pad := soft_px / GLOB.dpi_scaling dpi := GLOB.dpi_scaling prim := Primitive { - bounds = {center.x - radius_h - pad, center.y - radius_v - pad, - center.x + radius_h + pad, center.y + radius_v + pad}, + bounds = { + center.x - radius_h - pad, + center.y - radius_v - pad, + center.x + radius_h + pad, + center.y + radius_v + pad, + }, color = color, kind_flags = pack_kind_flags(.Ellipse, {}), } - prim.params.ellipse = Ellipse_Params{radii = {radius_h * dpi, radius_v * dpi}, soft_px = soft_px} + prim.params.ellipse = Ellipse_Params { + radii = {radius_h * dpi, radius_v * dpi}, + soft_px = soft_px, + } prepare_sdf_primitive(layer, prim) } @@ -494,13 +496,19 @@ ellipse_lines :: proc( dpi := GLOB.dpi_scaling prim := Primitive { - bounds = {center.x - radius_h - pad, center.y - radius_v - pad, - center.x + radius_h + pad, center.y + radius_v + pad}, + bounds = { + center.x - radius_h - pad, + center.y - radius_v - pad, + center.x + radius_h + pad, + center.y + radius_v + pad, + }, color = color, kind_flags = pack_kind_flags(.Ellipse, {.Stroke}), } - prim.params.ellipse = Ellipse_Params{ - radii = {radius_h * dpi, radius_v * dpi}, soft_px = soft_px, stroke_px = thick * dpi, + prim.params.ellipse = Ellipse_Params { + radii = {radius_h * dpi, radius_v * dpi}, + soft_px = soft_px, + stroke_px = thick * dpi, } prepare_sdf_primitive(layer, prim) } @@ -518,8 +526,12 @@ ring :: proc( dpi := GLOB.dpi_scaling prim := Primitive { - bounds = {center.x - outer_radius - pad, center.y - outer_radius - pad, - center.x + outer_radius + pad, center.y + outer_radius + pad}, + bounds = { + center.x - outer_radius - pad, + center.y - outer_radius - pad, + center.x + outer_radius + pad, + center.y + outer_radius + pad, + }, color = color, kind_flags = pack_kind_flags(.Ring_Arc, {}), } @@ -544,11 +556,27 @@ ring_lines :: proc( soft_px: f32 = 1.0, ) { // Inner arc outline - ring(layer, center, max(0, inner_radius - thick * 0.5), inner_radius + thick * 0.5, - start_angle, end_angle, color, soft_px) + ring( + layer, + center, + max(0, inner_radius - thick * 0.5), + inner_radius + thick * 0.5, + start_angle, + end_angle, + color, + soft_px, + ) // Outer arc outline - ring(layer, center, max(0, outer_radius - thick * 0.5), outer_radius + thick * 0.5, - start_angle, end_angle, color, soft_px) + ring( + layer, + center, + max(0, outer_radius - thick * 0.5), + outer_radius + thick * 0.5, + start_angle, + end_angle, + color, + soft_px, + ) // Start cap start_rad := math.to_radians(start_angle) end_rad := math.to_radians(end_angle) @@ -562,13 +590,7 @@ ring_lines :: proc( } // Draw a line segment via SDF. -line :: proc( - layer: ^Layer, - start, end_pos: [2]f32, - color: Color, - thick: f32 = 1, - soft_px: f32 = 1.0, -) { +line :: proc(layer: ^Layer, start, end_pos: [2]f32, color: Color, thick: f32 = 1, soft_px: f32 = 1.0) { cap := thick * 0.5 + soft_px / GLOB.dpi_scaling min_x := min(start.x, end_pos.x) - cap max_x := max(start.x, end_pos.x) + cap @@ -595,13 +617,7 @@ line :: proc( } // Draw a line strip via decomposed SDF segments. -line_strip :: proc( - layer: ^Layer, - points: [][2]f32, - color: Color, - thick: f32 = 1, - soft_px: f32 = 1.0, -) { +line_strip :: proc(layer: ^Layer, points: [][2]f32, color: Color, thick: f32 = 1, soft_px: f32 = 1.0) { if len(points) < 2 do return for i in 0 ..< len(points) - 1 { line(layer, points[i], points[i + 1], color, thick, soft_px) @@ -623,8 +639,12 @@ poly :: proc( dpi := GLOB.dpi_scaling prim := Primitive { - bounds = {center.x - radius - pad, center.y - radius - pad, - center.x + radius + pad, center.y + radius + pad}, + bounds = { + center.x - radius - pad, + center.y - radius - pad, + center.x + radius + pad, + center.y + radius + pad, + }, color = color, kind_flags = pack_kind_flags(.NGon, {}), } @@ -653,8 +673,12 @@ poly_lines :: proc( dpi := GLOB.dpi_scaling prim := Primitive { - bounds = {center.x - radius - pad, center.y - radius - pad, - center.x + radius + pad, center.y + radius + pad}, + bounds = { + center.x - radius - pad, + center.y - radius - pad, + center.x + radius + pad, + center.y + radius + pad, + }, color = color, kind_flags = pack_kind_flags(.NGon, {.Stroke}), }