Backdrop scope implementation (#25)
Co-authored-by: Zachary Levy <zachary@sunforge.is> Reviewed-on: #25
This commit was merged in pull request #25.
This commit is contained in:
+63
-39
@@ -392,16 +392,36 @@ frame_has_backdrop :: proc() -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Returns the absolute index of the first .Backdrop sub-batch in the layer's sub-batch range,
|
||||
// or -1 if the layer has no backdrops. The index is into GLOB.tmp_sub_batches (not relative to
|
||||
// layer.sub_batch_start), to match how draw_layer's render-range helpers consume it.
|
||||
// Find the scissor that owns a given sub-batch index by linear scan over GLOB.scissors.
|
||||
// Used by `run_backdrop_bracket`'s composite pass when the bracket loses its layer-pointer
|
||||
// context: per-sub-batch scissor lookup is required to honor scissors set up upstream by
|
||||
// `prepare_clay_batch`'s ScissorStart handling. O(scissors) per sub-batch is acceptable
|
||||
// because scissor counts are small (single digits in typical UI frames).
|
||||
//
|
||||
// Panics if no scissor owns the index. The renderer's invariant is that the scissor list
|
||||
// forms a contiguous, disjoint cover over `[0, len(tmp_sub_batches))` because every
|
||||
// sub-batch is created via `append_or_extend_sub_batch` (which increments the active
|
||||
// scissor's `sub_batch_len` in lockstep with the global array's growth) and scissors are
|
||||
// only created at the current end-of-array. A miss here means that invariant is broken —
|
||||
// either by a future code change that bypasses `append_or_extend_sub_batch`, by a scissor
|
||||
// constructed with the wrong `sub_batch_start`, or by external corruption — and silent
|
||||
// degradation would mask the bug. The panic message includes the offending index and the
|
||||
// scissor list shape so the failure is locatable.
|
||||
//INTERNAL
|
||||
find_first_backdrop_in_layer :: proc(layer: ^Layer) -> int {
|
||||
for i in 0 ..< layer.sub_batch_len {
|
||||
abs_idx := layer.sub_batch_start + i
|
||||
if GLOB.tmp_sub_batches[abs_idx].kind == .Backdrop do return int(abs_idx)
|
||||
find_scissor_for_sub_batch :: proc(sub_batch_index: u32) -> sdl.Rect {
|
||||
for scissor in GLOB.scissors {
|
||||
if sub_batch_index >= scissor.sub_batch_start &&
|
||||
sub_batch_index < scissor.sub_batch_start + scissor.sub_batch_len {
|
||||
return scissor.bounds
|
||||
}
|
||||
}
|
||||
return -1
|
||||
log.panicf(
|
||||
"find_scissor_for_sub_batch: no scissor owns sub-batch index %d (scissor count=%d, total sub-batches=%d); " +
|
||||
"the scissor list must form a contiguous cover over all sub-batches",
|
||||
sub_batch_index,
|
||||
len(GLOB.scissors),
|
||||
len(GLOB.tmp_sub_batches),
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
@@ -754,14 +774,16 @@ compute_backdrop_group_work_region :: proc(
|
||||
return
|
||||
}
|
||||
|
||||
// Run the backdrop bracket for one layer. Assumes:
|
||||
// - source_texture currently holds the pre-bracket frame contents (Pass A has already
|
||||
// rendered everything that should appear behind the backdrop).
|
||||
// Run one bracket over a contiguous range of pure-backdrop sub-batches. Assumes:
|
||||
// - source_texture currently holds the pre-bracket frame contents (everything submitted
|
||||
// ahead of this bracket on the same layer has already been rendered).
|
||||
// - The caller has invoked ensure_backdrop_textures with current swapchain dimensions.
|
||||
// - At least one .Backdrop sub-batch exists in the layer (caller checked).
|
||||
// - The half-open range `[sub_batch_start, sub_batch_end)` is non-empty and every
|
||||
// sub-batch in it has kind == .Backdrop. The caller (draw_layer) guarantees this by
|
||||
// splitting the layer into runs.
|
||||
//
|
||||
// Per-sigma-group execution. The bracket walks the layer's sub-batches in submission order,
|
||||
// grouping contiguous-same-sigma .Backdrop sub-batches. For each group:
|
||||
// Per-sigma-group execution. The bracket walks the range in submission order, grouping
|
||||
// contiguous-same-sigma .Backdrop sub-batches. For each group:
|
||||
// 1. Pick a downsample factor using compute_backdrop_downsample_factor.
|
||||
// 2. Compute that group's work region (primitives' AABB + 6σ halo, clamped).
|
||||
// 3. Downsample: source_texture → downsample_texture, viewport-limited to
|
||||
@@ -770,21 +792,22 @@ compute_backdrop_group_work_region :: proc(
|
||||
// 5. V-blur (mode 0, direction=V): h_blur_texture → downsample_texture (ping-pong reuse;
|
||||
// downsample_texture's data is no longer needed). Same viewport.
|
||||
// 6. Composite (mode 1): downsample_texture (now holds H+V blur) → source_texture, full-
|
||||
// target viewport, per-primitive SDF discard handles masking and applies the tint. Each
|
||||
// sub-batch in the group is one instanced draw.
|
||||
// target viewport, per-primitive SDF discard handles masking and applies the tint.
|
||||
// Each sub-batch in the group issues an instanced draw under its own scissor (sub-
|
||||
// batches inherit scissor state from the surrounding ScissorStart/End at submission).
|
||||
//
|
||||
// V-blur is run as its own working→working pass rather than folded into the composite. The
|
||||
// folded variant produces a horizontal-vs-vertical asymmetry artifact (horizontal source
|
||||
// features end up looking sharper than vertical ones inside the panel). Matching V's
|
||||
// structure exactly to H's restores symmetry.
|
||||
//
|
||||
// On exit, source_texture contains the pre-bracket contents plus all backdrop primitives
|
||||
// composited on top. The caller then runs Pass B (post-bracket non-backdrop sub-batches) on
|
||||
// source_texture with LOAD.
|
||||
// On exit, source_texture contains the pre-bracket contents plus all backdrop primitives in
|
||||
// this range composited on top.
|
||||
//INTERNAL
|
||||
run_backdrop_bracket :: proc(
|
||||
cmd_buffer: ^sdl.GPUCommandBuffer,
|
||||
layer: ^Layer,
|
||||
sub_batch_start: u32,
|
||||
sub_batch_end: u32,
|
||||
swapchain_width, swapchain_height: u32,
|
||||
) {
|
||||
pipeline := &GLOB.backdrop
|
||||
@@ -797,32 +820,23 @@ run_backdrop_bracket :: proc(
|
||||
min_depth = 0,
|
||||
max_depth = 1,
|
||||
}
|
||||
full_scissor := sdl.Rect {
|
||||
x = 0,
|
||||
y = 0,
|
||||
w = i32(swapchain_width),
|
||||
h = i32(swapchain_height),
|
||||
}
|
||||
|
||||
// Working textures are at full swapchain resolution. Each per-group factor=N pass writes
|
||||
// only to a sub-rect of dimensions (work_region_phys / N), via viewport-limited rendering.
|
||||
|
||||
layer_end := layer.sub_batch_start + layer.sub_batch_len
|
||||
i := layer.sub_batch_start
|
||||
layer_end := sub_batch_end
|
||||
i := sub_batch_start
|
||||
for i < layer_end {
|
||||
// Caller guarantees this range is pure backdrop sub-batches.
|
||||
assert(GLOB.tmp_sub_batches[i].kind == .Backdrop, "non-backdrop sub-batch inside bracket range")
|
||||
batch := GLOB.tmp_sub_batches[i]
|
||||
if batch.kind != .Backdrop {
|
||||
i += 1
|
||||
continue
|
||||
}
|
||||
|
||||
// Find the contiguous run of .Backdrop sub-batches with this sigma.
|
||||
sigma := batch.gaussian_sigma
|
||||
group_start := i
|
||||
group_end := i + 1
|
||||
for group_end < layer_end {
|
||||
next := GLOB.tmp_sub_batches[group_end]
|
||||
if next.kind != .Backdrop || next.gaussian_sigma != sigma do break
|
||||
if GLOB.tmp_sub_batches[group_end].gaussian_sigma != sigma do break
|
||||
group_end += 1
|
||||
}
|
||||
|
||||
@@ -997,6 +1011,10 @@ run_backdrop_bracket :: proc(
|
||||
// upsamples (via bilinear filtering on the read), applies the SDF mask, and applies the
|
||||
// tint. One render pass for the whole sigma group; each sub-batch issues its own draw
|
||||
// call because non-contiguous-but-same-sigma sub-batches couldn't coalesce upstream.
|
||||
//
|
||||
// Per-sub-batch scissor: sub-batches inherit scissor state from ScissorStart/End that
|
||||
// surrounded their submission. Switching scissors mid-pass is cheap; what matters is
|
||||
// that the composite respects the same clipping the caller set up.
|
||||
{
|
||||
frag_uniforms.mode = 1
|
||||
// direction is unused in mode 1 but keep it set so reading the uniform doesn't see
|
||||
@@ -1011,7 +1029,6 @@ run_backdrop_bracket :: proc(
|
||||
)
|
||||
sdl.BindGPUGraphicsPipeline(pass, pipeline.blur_pipeline)
|
||||
sdl.SetGPUViewport(pass, full_viewport)
|
||||
sdl.SetGPUScissor(pass, full_scissor)
|
||||
push_backdrop_vert_globals(cmd_buffer, f32(swapchain_width), f32(swapchain_height), 1)
|
||||
push_backdrop_blur_frag_globals(cmd_buffer, &frag_uniforms)
|
||||
sdl.BindGPUVertexStorageBuffers(pass, 0, ([^]^sdl.GPUBuffer)(&pipeline.primitive_buffer.gpu), 1)
|
||||
@@ -1021,8 +1038,17 @@ run_backdrop_bracket :: proc(
|
||||
&sdl.GPUTextureSamplerBinding{texture = pipeline.downsample_texture, sampler = pipeline.sampler},
|
||||
1,
|
||||
)
|
||||
|
||||
current_scissor: sdl.Rect = {0, 0, 0, 0}
|
||||
scissor_set := false
|
||||
for j in group_start ..< group_end {
|
||||
grp := GLOB.tmp_sub_batches[j]
|
||||
sub_batch_scissor := find_scissor_for_sub_batch(j)
|
||||
if !scissor_set || sub_batch_scissor != current_scissor {
|
||||
sdl.SetGPUScissor(pass, sub_batch_scissor)
|
||||
current_scissor = sub_batch_scissor
|
||||
scissor_set = true
|
||||
}
|
||||
sdl.DrawGPUPrimitives(pass, 6, grp.count, 0, grp.offset)
|
||||
}
|
||||
sdl.EndGPURenderPass(pass)
|
||||
@@ -1129,10 +1155,8 @@ prepare_backdrop_primitive :: proc(layer: ^Layer, prim: Gaussian_Blur_Primitive,
|
||||
// pass pair via sub-batch coalescing. Primitives with different sigmas in the same layer
|
||||
// trigger separate blur passes (cost scales with the number of unique sigmas).
|
||||
//
|
||||
// Submission ordering is asymmetric: a non-backdrop draw submitted between two backdrops in
|
||||
// the same layer renders *on top of* both backdrops, not between them. Use `draw.new_layer`
|
||||
// to interleave. See README.md § "Backdrop pipeline" for the full bracket scheduling model.
|
||||
gaussian_blur :: proc(
|
||||
// Must be called inside a `begin_backdrop` / `end_backdrop` scope (or use `backdrop_scope`).
|
||||
backdrop_blur :: proc(
|
||||
layer: ^Layer,
|
||||
rect: Rectangle,
|
||||
gaussian_sigma: f32,
|
||||
|
||||
Reference in New Issue
Block a user