GPU correctness fixes and optimizations

This commit is contained in:
Zachary Levy
2026-05-08 16:39:25 -07:00
parent c0fd79457c
commit 81ea3fd42c
3 changed files with 29 additions and 19 deletions
+7 -1
View File
@@ -684,7 +684,13 @@ upload_backdrop_primitives :: proc(device: ^sdl.GPUDevice, pass: ^sdl.GPUCopyPas
sdl.GPUBufferUsageFlags{.GRAPHICS_STORAGE_READ}, sdl.GPUBufferUsageFlags{.GRAPHICS_STORAGE_READ},
) )
prim_array := sdl.MapGPUTransferBuffer(device, GLOB.backdrop.primitive_buffer.transfer, false) // cycle=true: this is a persistent per-frame streaming transfer buffer. The previous
// frame's UploadToGPUBuffer is almost certainly still in flight when we map here
// (allowedFramesInFlight defaults to 2 on Metal). Without cycling, the CPU memcpy below
// races the GPU's blit read on the same MTLBuffer.contents. Cycling rebinds the
// container's active internal buffer to an unbound one (or allocates a new one) — O(1)
// in steady state, no fence wait. See SDL_gpu_metal.m's METAL_INTERNAL_PrepareBufferForWrite.
prim_array := sdl.MapGPUTransferBuffer(device, GLOB.backdrop.primitive_buffer.transfer, true)
if prim_array == nil { if prim_array == nil {
log.panicf("Failed to map backdrop primitive transfer buffer: %s", sdl.GetError()) log.panicf("Failed to map backdrop primitive transfer buffer: %s", sdl.GetError())
} }
+7 -3
View File
@@ -538,7 +538,9 @@ upload :: proc(device: ^sdl.GPUDevice, pass: ^sdl.GPUCopyPass) {
sdl.GPUBufferUsageFlags{.VERTEX}, sdl.GPUBufferUsageFlags{.VERTEX},
) )
vert_array := sdl.MapGPUTransferBuffer(device, GLOB.core_2d.vertex_buffer.transfer, false) // cycle=true: see backdrop.odin upload_backdrop_primitives. Persistent per-frame
// streaming buffer; previous frame's blit is still in flight at map time.
vert_array := sdl.MapGPUTransferBuffer(device, GLOB.core_2d.vertex_buffer.transfer, true)
if vert_array == nil { if vert_array == nil {
log.panicf("Failed to map vertex transfer buffer: %s", sdl.GetError()) log.panicf("Failed to map vertex transfer buffer: %s", sdl.GetError())
} }
@@ -569,7 +571,8 @@ upload :: proc(device: ^sdl.GPUDevice, pass: ^sdl.GPUCopyPass) {
grow_buffer_if_needed(device, &GLOB.core_2d.index_buffer, index_size, sdl.GPUBufferUsageFlags{.INDEX}) grow_buffer_if_needed(device, &GLOB.core_2d.index_buffer, index_size, sdl.GPUBufferUsageFlags{.INDEX})
idx_array := sdl.MapGPUTransferBuffer(device, GLOB.core_2d.index_buffer.transfer, false) // cycle=true: see vertex_buffer above.
idx_array := sdl.MapGPUTransferBuffer(device, GLOB.core_2d.index_buffer.transfer, true)
if idx_array == nil { if idx_array == nil {
log.panicf("Failed to map index transfer buffer: %s", sdl.GetError()) log.panicf("Failed to map index transfer buffer: %s", sdl.GetError())
} }
@@ -596,7 +599,8 @@ upload :: proc(device: ^sdl.GPUDevice, pass: ^sdl.GPUCopyPass) {
sdl.GPUBufferUsageFlags{.GRAPHICS_STORAGE_READ}, sdl.GPUBufferUsageFlags{.GRAPHICS_STORAGE_READ},
) )
prim_array := sdl.MapGPUTransferBuffer(device, GLOB.core_2d.primitive_buffer.transfer, false) // cycle=true: see vertex_buffer above.
prim_array := sdl.MapGPUTransferBuffer(device, GLOB.core_2d.primitive_buffer.transfer, true)
if prim_array == nil { if prim_array == nil {
log.panicf("Failed to map primitive transfer buffer: %s", sdl.GetError()) log.panicf("Failed to map primitive transfer buffer: %s", sdl.GetError())
} }
+15 -15
View File
@@ -279,12 +279,14 @@ hellope_custom :: proc() {
} }
gauge := Gauge { gauge := Gauge {
value = 0.73, value = 0.73,
color = {50, 200, 100, 255}, color = {50, 200, 100, 255},
bg_color = {80, 80, 80, 255},
} }
gauge2 := Gauge { gauge2 := Gauge {
value = 0.45, value = 0.45,
color = {200, 100, 50, 255}, color = {200, 100, 50, 255},
bg_color = {80, 80, 80, 255},
} }
// `clay.CustomElementConfig.customData` is a rawptr; the Clay integration in `draw` // `clay.CustomElementConfig.customData` is a rawptr; the Clay integration in `draw`
@@ -342,11 +344,11 @@ hellope_custom :: proc() {
// reflection inside the strip), and gauge2 is deferred-replayed by // reflection inside the strip), and gauge2 is deferred-replayed by
// `prepare_clay_batch` after the bracket closes (renders crisp on top of the // `prepare_clay_batch` after the bracket closes (renders crisp on top of the
// bracket output — unrelated to the strip since they don't overlap). // bracket output — unrelated to the strip since they don't overlap).
// `backgroundColor` is omitted on the gauges; bg lives on `Gauge.bg_color`. See `draw_custom`.
if clay.UI(clay.ID("gauge"))( if clay.UI(clay.ID("gauge"))(
{ {
layout = {sizing = {clay.SizingFixed(300), clay.SizingFixed(30)}}, layout = {sizing = {clay.SizingFixed(300), clay.SizingFixed(30)}},
custom = {customData = &gauge_custom}, custom = {customData = &gauge_custom},
backgroundColor = {80, 80, 80, 255},
}, },
) { ) {
if clay.UI(clay.ID("backdrop"))( if clay.UI(clay.ID("backdrop"))(
@@ -362,7 +364,6 @@ hellope_custom :: proc() {
{ {
layout = {sizing = {clay.SizingFixed(300), clay.SizingFixed(30)}}, layout = {sizing = {clay.SizingFixed(300), clay.SizingFixed(30)}},
custom = {customData = &gauge2_custom}, custom = {customData = &gauge2_custom},
backgroundColor = {80, 80, 80, 255},
}, },
) {} ) {}
} }
@@ -376,8 +377,9 @@ hellope_custom :: proc() {
} }
Gauge :: struct { Gauge :: struct {
value: f32, value: f32,
color: draw.Color, color: draw.Color,
bg_color: draw.Color,
} }
draw_custom :: proc(layer: ^draw.Layer, bounds: draw.Rectangle, render_data: clay.CustomRenderData) { draw_custom :: proc(layer: ^draw.Layer, bounds: draw.Rectangle, render_data: clay.CustomRenderData) {
@@ -386,14 +388,12 @@ hellope_custom :: proc() {
// before the union refactor. // before the union refactor.
gauge := cast(^Gauge)render_data.customData gauge := cast(^Gauge)render_data.customData
// `gauge.bg_color` instead of `render_data.backgroundColor`: under Clay master, an
// element with both `custom.customData` and `backgroundColor` emits a Custom AND a
// Rectangle for the same bounds, in that order — the Rectangle paints over the
// callback's output. Carrying bg on user data sidesteps it.
border_width: f32 = 2 border_width: f32 = 2
draw.rectangle( draw.rectangle(layer, bounds, gauge.bg_color, outline_color = draw.WHITE, outline_width = border_width)
layer,
bounds,
draw.color_from_clay(render_data.backgroundColor),
outline_color = draw.WHITE,
outline_width = border_width,
)
fill := draw.Rectangle { fill := draw.Rectangle {
x = bounds.x, x = bounds.x,