Backdrop scope implementation

This commit is contained in:
Zachary Levy
2026-05-01 17:46:49 -07:00
parent 5317b8f142
commit 05c6e6284c
5 changed files with 633 additions and 349 deletions
+53 -64
View File
@@ -598,6 +598,15 @@ upload :: proc(device: ^sdl.GPUDevice, pass: ^sdl.GPUCopyPass) {
//----- Layer dispatch ----------------------------------
// Walk the layer's sub-batches, alternating between non-backdrop runs (rendered to
// `render_texture` via `render_layer_sub_batch_range`) and backdrop runs (each closed by a
// `begin_backdrop`/`end_backdrop` scope at submission time, dispatched here as one bracket
// per run via `run_backdrop_bracket`).
//
// Multiple brackets per layer are allowed: each `begin_backdrop`/`end_backdrop` pair maps to
// one contiguous .Backdrop run in the sub-batch list, and non-backdrop draws between scopes
// render in their submission position relative to the brackets. This is the explicit-scope
// model that replaces the legacy single-bracket-per-layer scheduler.
//INTERNAL
draw_layer :: proc(
device: ^sdl.GPUDevice,
@@ -628,53 +637,44 @@ draw_layer :: proc(
return
}
bracket_start_abs := find_first_backdrop_in_layer(layer)
layer_end_abs := int(layer.sub_batch_start + layer.sub_batch_len)
// Walk sub-batches, alternating non-backdrop runs (rendered to render_texture) and
// backdrop runs (dispatched to run_backdrop_bracket). Each backdrop run corresponds to a
// single user-visible begin_backdrop/end_backdrop scope at submission time.
layer_start := int(layer.sub_batch_start)
layer_end := layer_start + int(layer.sub_batch_len)
i := layer_start
for i < layer_end {
// Find next non-backdrop run [run_start, run_end).
run_start := i
for i < layer_end && GLOB.tmp_sub_batches[i].kind != .Backdrop do i += 1
run_end := i
if run_end > run_start {
render_layer_sub_batch_range(
cmd_buffer,
render_texture,
swapchain_width,
swapchain_height,
clear_color,
layer,
run_start,
run_end,
)
}
if bracket_start_abs < 0 {
// Fast path: no backdrop in this layer; render the whole sub-batch range in one pass.
render_layer_sub_batch_range(
cmd_buffer,
render_texture,
swapchain_width,
swapchain_height,
clear_color,
layer,
int(layer.sub_batch_start),
layer_end_abs,
)
return
// Find next backdrop run [backdrop_scope_start, backdrop_scope_end). Each run = one bracket.
backdrop_scope_start := i
for i < layer_end && GLOB.tmp_sub_batches[i].kind == .Backdrop do i += 1
backdrop_scope_end := i
if backdrop_scope_end > backdrop_scope_start {
run_backdrop_bracket(
cmd_buffer,
u32(backdrop_scope_start),
u32(backdrop_scope_end),
swapchain_width,
swapchain_height,
)
}
}
// Bracketed layer: Pass A → backdrop bracket → Pass B.
// See README.md § "Backdrop pipeline" for the full ordering semantics.
render_layer_sub_batch_range(
cmd_buffer,
render_texture,
swapchain_width,
swapchain_height,
clear_color,
layer,
int(layer.sub_batch_start),
bracket_start_abs,
)
run_backdrop_bracket(cmd_buffer, layer, swapchain_width, swapchain_height)
// Pass B: render the [bracket_start_abs, layer_end_abs) range. .Backdrop sub-batches in
// this range are dispatched by the bracket above and ignored here (the .Backdrop case in
// the inner switch is a no-op). LOAD is implied because Pass A or the bracket's V-
// composite has already touched render_texture.
render_layer_sub_batch_range(
cmd_buffer,
render_texture,
swapchain_width,
swapchain_height,
clear_color,
layer,
bracket_start_abs,
layer_end_abs,
)
}
// Render a sub-range of a layer's sub-batches in a single render pass. Iterates the layer's
@@ -682,8 +682,10 @@ draw_layer :: proc(
// and `range_end_abs` parameters are absolute indices into GLOB.tmp_sub_batches; only sub-
// batches within `[range_start_abs, range_end_abs)` are drawn.
//
// .Backdrop sub-batches in the range are always silently skipped — they are dispatched by
// run_backdrop_bracket, not here. The empty .Backdrop case in the inner switch enforces this.
// The caller (`draw_layer`) splits the layer into pure-kind runs before calling this proc,
// so the range MUST NOT contain any .Backdrop sub-batches; backdrop dispatch is handled by
// `run_backdrop_bracket`. The .Backdrop case in the inner switch is `unreachable()` to
// surface contract violations as fast as possible.
//
// Render-pass setup mirrors the original draw_layer: clear-or-load based on GLOB.cleared,
// pipeline + storage + index buffer bound up front, then per-batch state tracking. After this
@@ -780,7 +782,7 @@ render_layer_sub_batch_range :: proc(
for abs_idx in effective_start ..< effective_end {
batch := &GLOB.tmp_sub_batches[abs_idx]
switch batch.kind {
#partial switch batch.kind {
case .Tessellated:
if current_mode != .Tessellated {
push_globals(cmd_buffer, width, height, .Tessellated)
@@ -869,10 +871,7 @@ render_layer_sub_batch_range :: proc(
}
sdl.DrawGPUPrimitives(render_pass, 6, batch.count, 0, batch.offset)
case .Backdrop:
// Always a no-op here. Backdrop sub-batches are dispatched by run_backdrop_bracket;
// when this proc encounters one (only possible in Pass B, since Pass A and the no-
// backdrop fast path both stop their range before any .Backdrop index), we skip it.
case: log.panicf("Non core2d batch kind (%v) reached in core2d dispatch path.", batch.kind)
}
}
}
@@ -894,21 +893,11 @@ prepare_shape :: proc(layer: ^Layer, vertices: []Vertex_2D) {
append_or_extend_sub_batch(scissor, layer, .Tessellated, offset, u32(len(vertices)))
}
// Submit an SDF primitive to the given layer for rendering. Requires the caller to build a
// Core_2D_Primitive directly, which is the internal GPU-layout struct.
//INTERNAL
prepare_sdf_primitive :: proc(layer: ^Layer, prim: Core_2D_Primitive) {
offset := u32(len(GLOB.tmp_primitives))
append(&GLOB.tmp_primitives, prim)
scissor := &GLOB.scissors[layer.scissor_start + layer.scissor_len - 1]
append_or_extend_sub_batch(scissor, layer, .SDF, offset, 1)
}
// Submit an SDF primitive with optional texture binding.
// The texture-aware counterpart of `prepare_sdf_primitive`; lets shape procs route a
// texture_id and sampler into the sub-batch without growing the public API.
//INTERNAL
prepare_sdf_primitive_ex :: proc(
prepare_sdf_primitive :: proc(
layer: ^Layer,
prim: Core_2D_Primitive,
texture_id: Texture_Id = INVALID_TEXTURE,
@@ -1409,7 +1398,7 @@ apply_brush_and_outline :: proc(
}
prim.flags = pack_kind_flags(kind, flags)
prepare_sdf_primitive_ex(layer, prim^, texture_id, sampler)
prepare_sdf_primitive(layer, prim^, texture_id, sampler)
}
// ---------------------------------------------------------------------------------------------------------------------