Added backdrop effects pipeline (blur)
This commit is contained in:
+133
-28
@@ -29,7 +29,7 @@ TextBatch :: struct {
|
||||
// ----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// The SDF path evaluates one of four signed distance functions per primitive, dispatched
|
||||
// by Shape_Kind encoded in the low byte of Primitive.flags:
|
||||
// by Shape_Kind encoded in the low byte of Base_2D_Primitive.flags:
|
||||
//
|
||||
// RRect — rounded rectangle with per-corner radii (sdRoundedBox). Also covers circles
|
||||
// (uniform radii = half-size), capsule-style line segments (rotated, max rounding),
|
||||
@@ -47,10 +47,10 @@ Shape_Kind :: enum u8 {
|
||||
}
|
||||
|
||||
Shape_Flag :: enum u8 {
|
||||
Textured, // bit 0: sample texture using uv.uv_rect (mutually exclusive with Gradient)
|
||||
Gradient, // bit 1: 2-color gradient using uv.effects.gradient_color as end/outer color
|
||||
Textured, // bit 0: sample texture using uv_rect (mutually exclusive with Gradient via Brush union)
|
||||
Gradient, // bit 1: 2-color gradient using effects.gradient_color as end/outer color
|
||||
Gradient_Radial, // bit 2: if set with Gradient, radial from center; else linear at angle
|
||||
Outline, // bit 3: outer outline band using uv.effects.outline_color; CPU expands bounds by outline_width
|
||||
Outline, // bit 3: outer outline band using effects.outline_color; CPU expands bounds by outline_width
|
||||
Rotated, // bit 4: shape has non-zero rotation; rotation_sc contains packed sin/cos
|
||||
Arc_Narrow, // bit 5: ring arc span ≤ π — intersect half-planes. Neither Arc bit = full ring.
|
||||
Arc_Wide, // bit 6: ring arc span > π — union half-planes. Neither Arc bit = full ring.
|
||||
@@ -97,7 +97,7 @@ Shape_Params :: struct #raw_union {
|
||||
#assert(size_of(Shape_Params) == 32)
|
||||
|
||||
// GPU-side storage for 2-color gradient parameters and/or outline parameters.
|
||||
// Packed into 16 bytes to alias with uv_rect in the Uv_Or_Effects raw union.
|
||||
// Packed into 16 bytes. Independent from uv_rect — texture and outline can coexist.
|
||||
// The shader reads gradient_color and outline_color via unpackUnorm4x8.
|
||||
// gradient_dir_sc stores the pre-computed gradient direction as (cos, sin) in f16 pair
|
||||
// via unpackHalf2x16. outline_packed stores outline_width as f16 via unpackHalf2x16.
|
||||
@@ -107,38 +107,33 @@ Gradient_Outline :: struct {
|
||||
gradient_dir_sc: u32, // 8: packed f16 pair: low = cos(angle), high = sin(angle) — pre-computed gradient direction
|
||||
outline_packed: u32, // 12: packed f16 pair: low = outline_width (f16, physical pixels), high = reserved
|
||||
}
|
||||
|
||||
#assert(size_of(Gradient_Outline) == 16)
|
||||
|
||||
// Uv_Or_Effects aliases the final 16 bytes of a Primitive. When .Textured is set,
|
||||
// uv_rect holds texture-atlas coordinates. When .Gradient or .Outline is set,
|
||||
// effects holds 2-color gradient parameters and/or outline parameters.
|
||||
// Textured and Gradient are mutually exclusive; if both are set, Gradient takes precedence.
|
||||
Uv_Or_Effects :: struct #raw_union {
|
||||
uv_rect: [4]f32, // u_min, v_min, u_max, v_max (default {0,0,1,1})
|
||||
effects: Gradient_Outline, // gradient + outline parameters
|
||||
}
|
||||
|
||||
// GPU layout: 80 bytes, std430-compatible. The shader declares this as a storage buffer struct.
|
||||
// GPU layout: 96 bytes, std430-compatible. The shader declares this as a storage buffer struct.
|
||||
// The low byte of `flags` encodes the Shape_Kind (0 = tessellated, 1-4 = SDF kinds).
|
||||
// Bits 8-15 encode Shape_Flags (Textured, Gradient, Gradient_Radial, Outline, Rotated, Arc_Narrow, Arc_Wide).
|
||||
// rotation_sc stores pre-computed sin/cos of the rotation angle as a packed f16 pair,
|
||||
// avoiding per-pixel trigonometry in the fragment shader. Only read when .Rotated is set.
|
||||
Primitive :: struct {
|
||||
//
|
||||
// Named Base_2D_Primitive (not just Primitive) to disambiguate from Backdrop_Primitive in
|
||||
// pipeline_2d_backdrop.odin. The two pipelines have unrelated GPU layouts and unrelated
|
||||
// fragment-shader contracts; pairing each with its own primitive type keeps cross-references
|
||||
// unambiguous when grepping the codebase.
|
||||
Base_2D_Primitive :: struct {
|
||||
bounds: [4]f32, // 0: min_x, min_y, max_x, max_y (world-space, pre-DPI)
|
||||
color: Color, // 16: u8x4, fill color / gradient start color / texture tint
|
||||
flags: u32, // 20: low byte = Shape_Kind, bits 8+ = Shape_Flags
|
||||
rotation_sc: u32, // 24: packed f16 pair: low = sin(angle), high = cos(angle). Requires .Rotated flag.
|
||||
_pad: f32, // 28: reserved for future use
|
||||
params: Shape_Params, // 32: per-kind shape parameters (raw union, 32 bytes)
|
||||
uv: Uv_Or_Effects, // 64: texture coords or gradient/outline parameters
|
||||
uv_rect: [4]f32, // 64: texture UV coordinates (u_min, v_min, u_max, v_max). Read when .Textured.
|
||||
effects: Gradient_Outline, // 80: gradient and/or outline parameters. Read when .Gradient and/or .Outline.
|
||||
}
|
||||
#assert(size_of(Base_2D_Primitive) == 96)
|
||||
|
||||
#assert(size_of(Primitive) == 80)
|
||||
|
||||
// Pack shape kind and flags into the Primitive.flags field. The low byte encodes the Shape_Kind
|
||||
// (which also serves as the SDF mode marker — kind > 0 means SDF path). The tessellated path
|
||||
// leaves the field at 0 (Solid kind, set by vertex shader zero-initialization).
|
||||
// Pack shape kind and flags into the Base_2D_Primitive.flags field. The low byte encodes the
|
||||
// Shape_Kind (which also serves as the SDF mode marker — kind > 0 means SDF path). The
|
||||
// tessellated path leaves the field at 0 (Solid kind, set by vertex shader zero-initialization).
|
||||
pack_kind_flags :: #force_inline proc(kind: Shape_Kind, flags: Shape_Flags) -> u32 {
|
||||
return u32(kind) | (u32(transmute(u8)flags) << 8)
|
||||
}
|
||||
@@ -159,11 +154,13 @@ Pipeline_2D_Base :: struct {
|
||||
sampler: ^sdl.GPUSampler,
|
||||
}
|
||||
|
||||
// MSAA is not supported by levlib (see init's doc comment in draw.odin); the PSO is hard-wired
|
||||
// to single-sample. SDF text and shapes provide analytical AA via smoothstep; tessellated user
|
||||
// geometry is not anti-aliased.
|
||||
@(private)
|
||||
create_pipeline_2d_base :: proc(
|
||||
device: ^sdl.GPUDevice,
|
||||
window: ^sdl.Window,
|
||||
sample_count: sdl.GPUSampleCount,
|
||||
) -> (
|
||||
pipeline: Pipeline_2D_Base,
|
||||
ok: bool,
|
||||
@@ -237,7 +234,7 @@ create_pipeline_2d_base :: proc(
|
||||
vertex_shader = vert_shader,
|
||||
fragment_shader = frag_shader,
|
||||
primitive_type = .TRIANGLELIST,
|
||||
multisample_state = sdl.GPUMultisampleState{sample_count = sample_count},
|
||||
multisample_state = sdl.GPUMultisampleState{sample_count = ._1},
|
||||
target_info = sdl.GPUGraphicsPipelineTargetInfo {
|
||||
color_target_descriptions = &sdl.GPUColorTargetDescription {
|
||||
format = sdl.GetGPUSwapchainTextureFormat(device, window),
|
||||
@@ -302,7 +299,7 @@ create_pipeline_2d_base :: proc(
|
||||
prim_buf_ok: bool
|
||||
pipeline.primitive_buffer, prim_buf_ok = create_buffer(
|
||||
device,
|
||||
size_of(Primitive) * BUFFER_INIT_SIZE,
|
||||
size_of(Base_2D_Primitive) * BUFFER_INIT_SIZE,
|
||||
sdl.GPUBufferUsageFlags{.GRAPHICS_STORAGE_READ},
|
||||
)
|
||||
if !prim_buf_ok do return pipeline, false
|
||||
@@ -505,7 +502,7 @@ upload :: proc(device: ^sdl.GPUDevice, pass: ^sdl.GPUCopyPass) {
|
||||
// Upload SDF primitives
|
||||
prim_count := u32(len(GLOB.tmp_primitives))
|
||||
if prim_count > 0 {
|
||||
prim_size := prim_count * size_of(Primitive)
|
||||
prim_size := prim_count * size_of(Base_2D_Primitive)
|
||||
|
||||
grow_buffer_if_needed(
|
||||
device,
|
||||
@@ -560,6 +557,101 @@ 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)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
// scissors and walks each scissor's sub-batches, dispatching by kind. The `range_start_abs`
|
||||
// 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.
|
||||
//
|
||||
// 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
|
||||
// proc returns, GLOB.cleared is guaranteed true.
|
||||
//
|
||||
// If the range is empty after filtering (no eligible sub-batches at all), this proc still
|
||||
// honors the no-clear-yet contract by issuing a clear-only pass when needed; otherwise it
|
||||
// returns without opening a render pass.
|
||||
@(private)
|
||||
render_layer_sub_batch_range :: proc(
|
||||
cmd_buffer: ^sdl.GPUCommandBuffer,
|
||||
render_texture: ^sdl.GPUTexture,
|
||||
swapchain_width: u32,
|
||||
swapchain_height: u32,
|
||||
clear_color: [4]f32,
|
||||
layer: ^Layer,
|
||||
range_start_abs: int,
|
||||
range_end_abs: int,
|
||||
) {
|
||||
if range_start_abs >= range_end_abs {
|
||||
// Empty range. If we still owe a clear, do a clear-only pass; otherwise nothing to do.
|
||||
if !GLOB.cleared {
|
||||
pass := sdl.BeginGPURenderPass(
|
||||
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,
|
||||
store_op = .STORE,
|
||||
},
|
||||
1,
|
||||
nil,
|
||||
)
|
||||
sdl.EndGPURenderPass(pass)
|
||||
GLOB.cleared = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
render_pass := sdl.BeginGPURenderPass(
|
||||
cmd_buffer,
|
||||
&sdl.GPUColorTargetInfo {
|
||||
@@ -611,9 +703,17 @@ draw_layer :: proc(
|
||||
text_vertex_gpu_base := u32(len(GLOB.tmp_shape_verts))
|
||||
|
||||
for &scissor in GLOB.scissors[layer.scissor_start:][:layer.scissor_len] {
|
||||
// Intersect this scissor's sub-batch span with the requested range.
|
||||
scissor_start := int(scissor.sub_batch_start)
|
||||
scissor_end := scissor_start + int(scissor.sub_batch_len)
|
||||
effective_start := max(scissor_start, range_start_abs)
|
||||
effective_end := min(scissor_end, range_end_abs)
|
||||
if effective_start >= effective_end do continue
|
||||
|
||||
sdl.SetGPUScissor(render_pass, scissor.bounds)
|
||||
|
||||
for &batch in GLOB.tmp_sub_batches[scissor.sub_batch_start:][:scissor.sub_batch_len] {
|
||||
for abs_idx in effective_start ..< effective_end {
|
||||
batch := &GLOB.tmp_sub_batches[abs_idx]
|
||||
switch batch.kind {
|
||||
case .Tessellated:
|
||||
if current_mode != .Tessellated {
|
||||
@@ -702,6 +802,11 @@ draw_layer :: proc(
|
||||
current_sampler = batch_sampler
|
||||
}
|
||||
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.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user