Added backdrop effects pipeline (blur)
This commit is contained in:
+130
-123
@@ -4,7 +4,6 @@ import "base:runtime"
|
||||
import "core:c"
|
||||
import "core:log"
|
||||
import "core:math"
|
||||
|
||||
import "core:strings"
|
||||
import sdl "vendor:sdl3"
|
||||
import sdl_ttf "vendor:sdl3/ttf"
|
||||
@@ -16,11 +15,19 @@ when ODIN_OS == .Darwin {
|
||||
SHADER_ENTRY :: cstring("main0")
|
||||
BASE_VERT_2D_RAW :: #load("shaders/generated/base_2d.vert.metal")
|
||||
BASE_FRAG_2D_RAW :: #load("shaders/generated/base_2d.frag.metal")
|
||||
BACKDROP_FULLSCREEN_VERT_RAW :: #load("shaders/generated/backdrop_fullscreen.vert.metal")
|
||||
BACKDROP_DOWNSAMPLE_FRAG_RAW :: #load("shaders/generated/backdrop_downsample.frag.metal")
|
||||
BACKDROP_BLUR_VERT_RAW :: #load("shaders/generated/backdrop_blur.vert.metal")
|
||||
BACKDROP_BLUR_FRAG_RAW :: #load("shaders/generated/backdrop_blur.frag.metal")
|
||||
} else {
|
||||
PLATFORM_SHADER_FORMAT_FLAG :: sdl.GPUShaderFormatFlag.SPIRV
|
||||
SHADER_ENTRY :: cstring("main")
|
||||
BASE_VERT_2D_RAW :: #load("shaders/generated/base_2d.vert.spv")
|
||||
BASE_FRAG_2D_RAW :: #load("shaders/generated/base_2d.frag.spv")
|
||||
BACKDROP_FULLSCREEN_VERT_RAW :: #load("shaders/generated/backdrop_fullscreen.vert.spv")
|
||||
BACKDROP_DOWNSAMPLE_FRAG_RAW :: #load("shaders/generated/backdrop_downsample.frag.spv")
|
||||
BACKDROP_BLUR_VERT_RAW :: #load("shaders/generated/backdrop_blur.vert.spv")
|
||||
BACKDROP_BLUR_FRAG_RAW :: #load("shaders/generated/backdrop_blur.frag.spv")
|
||||
}
|
||||
PLATFORM_SHADER_FORMAT :: sdl.GPUShaderFormat{PLATFORM_SHADER_FORMAT_FLAG}
|
||||
|
||||
@@ -28,10 +35,6 @@ BUFFER_INIT_SIZE :: 256
|
||||
INITIAL_LAYER_SIZE :: 5
|
||||
INITIAL_SCISSOR_SIZE :: 10
|
||||
|
||||
// Sentinel value: when passed as msaa_samples, `init` will use the maximum MSAA sample count
|
||||
// supported by the GPU for the swapchain format.
|
||||
MSAA_MAX :: sdl.GPUSampleCount(0xFF)
|
||||
|
||||
// ----- Default parameter values -----
|
||||
// Named constants for non-zero default procedure parameters. Centralizes magic numbers
|
||||
// so they can be tuned in one place and referenced by name in proc signatures.
|
||||
@@ -39,8 +42,8 @@ DFT_FEATHER_PX :: 1 // Total AA feather width in physical pixels (half on each s
|
||||
DFT_STROKE_THICKNESS :: 1 // Default line/stroke thickness in logical pixels.
|
||||
DFT_FONT_SIZE :: 44 // Default font size in points for text rendering.
|
||||
DFT_CIRC_END_ANGLE :: 360 // Full-circle end angle in degrees (ring/arc).
|
||||
DFT_UV_RECT :: Rectangle{0, 0, 1, 1} // Full-texture UV rect (rectangle_texture).
|
||||
DFT_TINT :: WHITE // Default texture tint (rectangle_texture, clay_image).
|
||||
DFT_UV_RECT :: Rectangle{0, 0, 1, 1} // Full-texture UV rect (Texture_Fill default).
|
||||
DFT_TINT :: WHITE // Default texture tint (Texture_Fill, clay_image).
|
||||
DFT_TEXT_COLOR :: BLACK // Default text color.
|
||||
DFT_CLEAR_COLOR :: BLACK // Default clear color for end().
|
||||
DFT_SAMPLER :: Sampler_Preset.Linear_Clamp // Default texture sampler preset.
|
||||
@@ -53,9 +56,10 @@ Global :: struct {
|
||||
tmp_text_verts: [dynamic]Vertex, // Text vertices staged for GPU upload.
|
||||
tmp_text_indices: [dynamic]c.int, // Text index buffer staged for GPU upload.
|
||||
tmp_text_batches: [dynamic]TextBatch, // Text atlas batch metadata for indexed drawing.
|
||||
tmp_primitives: [dynamic]Primitive, // SDF primitives staged for GPU storage buffer upload.
|
||||
tmp_primitives: [dynamic]Base_2D_Primitive, // SDF primitives staged for GPU storage buffer upload (base 2D pipeline).
|
||||
tmp_sub_batches: [dynamic]Sub_Batch, // Sub-batch records that drive draw call dispatch.
|
||||
tmp_uncached_text: [dynamic]^sdl_ttf.Text, // Uncached TTF_Text objects destroyed after end() submits.
|
||||
tmp_backdrop_primitives: [dynamic]Backdrop_Primitive, // Backdrop primitives staged for GPU storage buffer upload.
|
||||
layers: [dynamic]Layer, // Draw layers, each with its own scissor stack.
|
||||
scissors: [dynamic]Scissor, // Scissor rects that clip drawing within each layer.
|
||||
|
||||
@@ -67,6 +71,7 @@ Global :: struct {
|
||||
|
||||
// -- Pipeline (accessed every draw_layer call) --
|
||||
pipeline_2d_base: Pipeline_2D_Base, // The unified 2D GPU pipeline (shaders, buffers, samplers).
|
||||
pipeline_2d_backdrop: Pipeline_2D_Backdrop, // Frosted-glass backdrop blur pipeline (downsample + blur PSOs, working textures).
|
||||
device: ^sdl.GPUDevice, // GPU device handle, stored at init.
|
||||
samplers: [SAMPLER_PRESET_COUNT]^sdl.GPUSampler, // Lazily-created sampler objects, one per Sampler_Preset.
|
||||
|
||||
@@ -78,12 +83,6 @@ Global :: struct {
|
||||
texture_slots: [dynamic]Texture_Slot, // Registered texture slots indexed by Texture_Id.
|
||||
texture_free_list: [dynamic]u32, // Recycled slot indices available for reuse.
|
||||
|
||||
// -- MSAA (once per frame in end()) --
|
||||
msaa_texture: ^sdl.GPUTexture, // Intermediate render target for multi-sample resolve.
|
||||
msaa_width: u32, // Cached width to detect when MSAA texture needs recreation.
|
||||
msaa_height: u32, // Cached height to detect when MSAA texture needs recreation.
|
||||
sample_count: sdl.GPUSampleCount, // Sample count chosen at init (._1 means MSAA disabled).
|
||||
|
||||
// -- Clay (once per frame in prepare_clay_batch) --
|
||||
clay_memory: [^]u8, // Raw memory block backing Clay's internal arena.
|
||||
|
||||
@@ -99,6 +98,7 @@ Global :: struct {
|
||||
max_text_batches: int,
|
||||
max_primitives: int,
|
||||
max_sub_batches: int,
|
||||
max_backdrop_primitives: int,
|
||||
|
||||
// -- Init-only (coldest — set once at init, never written again) --
|
||||
odin_context: runtime.Context, // Odin context captured at init for use in callbacks.
|
||||
@@ -128,8 +128,8 @@ Vec2 :: [2]f32
|
||||
// transparent. This matches the GPU-side layout: the shader unpacks via unpackUnorm4x8 which
|
||||
// reads the bytes in memory order as R, G, B, A and normalizes each to [0, 1].
|
||||
//
|
||||
// When used in the Primitive struct (Primitive.color), the 4 bytes are stored as a u32 in
|
||||
// native byte order and unpacked by the shader.
|
||||
// When used in the Base_2D_Primitive or Backdrop_Primitive structs (e.g. .color), the 4 bytes
|
||||
// are stored as a u32 in native byte order and unpacked by the shader.
|
||||
Color :: [4]u8
|
||||
|
||||
BLACK :: Color{0, 0, 0, 255}
|
||||
@@ -149,29 +149,42 @@ Rectangle_Radii :: struct {
|
||||
}
|
||||
|
||||
// A linear gradient between two colors along an arbitrary angle.
|
||||
// The `end_color` is the color at the end of the gradient direction; the shape's fill `color`
|
||||
// parameter acts as the start color. `angle` is in degrees: 0 = left-to-right, 90 = top-to-bottom.
|
||||
// `angle` is in degrees: 0 = left-to-right, 90 = top-to-bottom.
|
||||
Linear_Gradient :: struct {
|
||||
end_color: Color,
|
||||
angle: f32,
|
||||
start_color: Color,
|
||||
end_color: Color,
|
||||
angle: f32,
|
||||
}
|
||||
|
||||
// A radial gradient between two colors from center to edge.
|
||||
// The `outer_color` is the color at the shape's edge; the shape's fill `color` parameter
|
||||
// acts as the inner (center) color.
|
||||
Radial_Gradient :: struct {
|
||||
inner_color: Color,
|
||||
outer_color: Color,
|
||||
}
|
||||
|
||||
// Tagged union for specifying a gradient on any shape. Defaults to `nil` (no gradient).
|
||||
// When a gradient is active, the shape's `color` parameter becomes the start/inner color,
|
||||
// and the gradient struct carries the end/outer color plus any type-specific parameters.
|
||||
//
|
||||
// Gradient and Textured are mutually exclusive on the same primitive. If a shape uses
|
||||
// `rectangle_texture`, gradients are not applicable — use the tint color instead.
|
||||
Gradient :: union {
|
||||
// Sample a registered texture as the shape's fill source.
|
||||
// `tint` modulates the sampled texels per-pixel (constant multiply); WHITE passes through
|
||||
// unchanged. Translucent tints fade the texture; non-white tints recolor it.
|
||||
// Zero-initialized fields are treated as defaults by the shape procs:
|
||||
// tint == Color{} → WHITE
|
||||
// uv_rect == Rectangle{} → {0, 0, 1, 1} (full texture)
|
||||
// sampler == .Linear_Clamp (enum value 0)
|
||||
Texture_Fill :: struct {
|
||||
id: Texture_Id,
|
||||
tint: Color,
|
||||
uv_rect: Rectangle,
|
||||
sampler: Sampler_Preset,
|
||||
}
|
||||
|
||||
// Mutually exclusive fill sources for shape procs. Each shape proc accepts a Brush
|
||||
// as its third positional parameter. Texture and gradient are mutually exclusive at
|
||||
// the GPU level (they share the worst-case register path); outline is orthogonal and
|
||||
// composes with any Brush variant.
|
||||
Brush :: union {
|
||||
Color,
|
||||
Linear_Gradient,
|
||||
Radial_Gradient,
|
||||
Texture_Fill,
|
||||
}
|
||||
|
||||
// Convert clay.Color ([4]c.float in 0–255 range) to Color.
|
||||
@@ -207,17 +220,24 @@ Rectangle :: struct {
|
||||
}
|
||||
|
||||
Sub_Batch_Kind :: enum u8 {
|
||||
Tessellated, // non-indexed, white texture or user texture, mode 0
|
||||
Text, // indexed, atlas texture, mode 0
|
||||
SDF, // instanced unit quad, white texture or user texture, mode 1
|
||||
Tessellated, // non-indexed, white texture or user texture, base 2D mode 0
|
||||
Text, // indexed, atlas texture, base 2D mode 0
|
||||
SDF, // instanced unit quad, base 2D mode 1
|
||||
// instanced unit quad, backdrop pipeline V-composite (indexes Backdrop_Primitive).
|
||||
// Bracket-scheduled per layer; see README.md § "Backdrop pipeline" for ordering semantics.
|
||||
Backdrop,
|
||||
}
|
||||
|
||||
Sub_Batch :: struct {
|
||||
kind: Sub_Batch_Kind,
|
||||
offset: u32, // Tessellated: vertex offset; Text: text_batch index; SDF: primitive index
|
||||
count: u32, // Tessellated: vertex count; Text: always 1; SDF: primitive count
|
||||
texture_id: Texture_Id,
|
||||
sampler: Sampler_Preset,
|
||||
kind: Sub_Batch_Kind,
|
||||
offset: u32, // Tessellated: vertex offset; Text: text_batch index; SDF/Backdrop: primitive index
|
||||
count: u32, // Tessellated: vertex count; Text: always 1; SDF/Backdrop: primitive count
|
||||
texture_id: Texture_Id,
|
||||
sampler: Sampler_Preset,
|
||||
// Backdrop only — Gaussian std-dev in logical pixels. Named with the
|
||||
// distribution prefix because future kinds may want different sigma
|
||||
// shapes (e.g. drop-shadow penumbra) without overloading this field.
|
||||
gaussian_sigma: f32,
|
||||
}
|
||||
|
||||
Layer :: struct {
|
||||
@@ -234,39 +254,38 @@ Scissor :: struct {
|
||||
sub_batch_len: u32,
|
||||
}
|
||||
|
||||
Init_Options :: struct {
|
||||
// MSAA sample count. Default is ._1 (no MSAA). SDF rendering does not benefit from MSAA
|
||||
// because SDF fragments compute coverage analytically via `smoothstep`. MSAA helps for
|
||||
// text glyph edges and tessellated user geometry. Set to ._4 or ._8 for text-heavy UIs,
|
||||
// or use `MSAA_MAX` to request the highest sample count the GPU supports for the swapchain
|
||||
// format.
|
||||
msaa_samples: sdl.GPUSampleCount,
|
||||
}
|
||||
|
||||
// Initialize the renderer. Returns false if GPU pipeline or text engine creation fails.
|
||||
//
|
||||
// MSAA is intentionally NOT supported. SDF text and shapes compute coverage analytically via
|
||||
// `smoothstep`, so they don't benefit from multisampling. Tessellated user geometry submitted
|
||||
// via `prepare_shape` is not anti-aliased — if you need AA on tessellated content, render it
|
||||
// to your own offscreen target and submit it as a texture. RAD Debugger and the SBC target
|
||||
// (Mali Valhall, where MSAA's per-tile bandwidth multiplier is expensive) drove this decision.
|
||||
@(require_results)
|
||||
init :: proc(
|
||||
device: ^sdl.GPUDevice,
|
||||
window: ^sdl.Window,
|
||||
options: Init_Options = {},
|
||||
allocator := context.allocator,
|
||||
odin_context := context,
|
||||
) -> (
|
||||
ok: bool,
|
||||
) {
|
||||
min_memory_size: c.size_t = cast(c.size_t)clay.MinMemorySize()
|
||||
resolved_sample_count := options.msaa_samples
|
||||
if resolved_sample_count == MSAA_MAX {
|
||||
resolved_sample_count = max_sample_count(device, window)
|
||||
|
||||
pipeline, pipeline_ok := create_pipeline_2d_base(device, window)
|
||||
if !pipeline_ok {
|
||||
return false
|
||||
}
|
||||
|
||||
pipeline, pipeline_ok := create_pipeline_2d_base(device, window, resolved_sample_count)
|
||||
if !pipeline_ok {
|
||||
backdrop_pipeline, backdrop_pipeline_ok := create_pipeline_2d_backdrop(device, window)
|
||||
if !backdrop_pipeline_ok {
|
||||
destroy_pipeline_2d_base(device, &pipeline)
|
||||
return false
|
||||
}
|
||||
|
||||
text_cache, text_ok := init_text_cache(device, allocator)
|
||||
if !text_ok {
|
||||
destroy_pipeline_2d_backdrop(device, &backdrop_pipeline)
|
||||
destroy_pipeline_2d_base(device, &pipeline)
|
||||
return false
|
||||
}
|
||||
@@ -278,9 +297,10 @@ init :: proc(
|
||||
tmp_text_verts = make([dynamic]Vertex, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_text_indices = make([dynamic]c.int, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_text_batches = make([dynamic]TextBatch, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_primitives = make([dynamic]Primitive, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_primitives = make([dynamic]Base_2D_Primitive, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_sub_batches = make([dynamic]Sub_Batch, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
tmp_uncached_text = make([dynamic]^sdl_ttf.Text, 0, 16, allocator = allocator),
|
||||
tmp_backdrop_primitives = make([dynamic]Backdrop_Primitive, 0, BUFFER_INIT_SIZE, allocator = allocator),
|
||||
device = device,
|
||||
texture_slots = make([dynamic]Texture_Slot, 0, 16, allocator = allocator),
|
||||
texture_free_list = make([dynamic]u32, 0, 16, allocator = allocator),
|
||||
@@ -289,8 +309,8 @@ init :: proc(
|
||||
odin_context = odin_context,
|
||||
dpi_scaling = sdl.GetWindowDisplayScale(window),
|
||||
clay_memory = make([^]u8, min_memory_size, allocator = allocator),
|
||||
sample_count = resolved_sample_count,
|
||||
pipeline_2d_base = pipeline,
|
||||
pipeline_2d_backdrop = backdrop_pipeline,
|
||||
text_cache = text_cache,
|
||||
}
|
||||
|
||||
@@ -325,6 +345,8 @@ resize_global :: proc() {
|
||||
shrink(&GLOB.tmp_primitives, GLOB.max_primitives)
|
||||
if len(GLOB.tmp_sub_batches) > GLOB.max_sub_batches do GLOB.max_sub_batches = len(GLOB.tmp_sub_batches)
|
||||
shrink(&GLOB.tmp_sub_batches, GLOB.max_sub_batches)
|
||||
if len(GLOB.tmp_backdrop_primitives) > GLOB.max_backdrop_primitives do GLOB.max_backdrop_primitives = len(GLOB.tmp_backdrop_primitives)
|
||||
shrink(&GLOB.tmp_backdrop_primitives, GLOB.max_backdrop_primitives)
|
||||
}
|
||||
|
||||
destroy :: proc(device: ^sdl.GPUDevice, allocator := context.allocator) {
|
||||
@@ -336,17 +358,16 @@ destroy :: proc(device: ^sdl.GPUDevice, allocator := context.allocator) {
|
||||
delete(GLOB.tmp_text_batches)
|
||||
delete(GLOB.tmp_primitives)
|
||||
delete(GLOB.tmp_sub_batches)
|
||||
delete(GLOB.tmp_backdrop_primitives)
|
||||
for ttf_text in GLOB.tmp_uncached_text do sdl_ttf.DestroyText(ttf_text)
|
||||
delete(GLOB.tmp_uncached_text)
|
||||
free(GLOB.clay_memory, allocator)
|
||||
if GLOB.msaa_texture != nil {
|
||||
sdl.ReleaseGPUTexture(device, GLOB.msaa_texture)
|
||||
}
|
||||
process_pending_texture_releases()
|
||||
destroy_all_textures()
|
||||
destroy_sampler_pool()
|
||||
for ttf_text in GLOB.pending_text_releases do sdl_ttf.DestroyText(ttf_text)
|
||||
delete(GLOB.pending_text_releases)
|
||||
destroy_pipeline_2d_backdrop(device, &GLOB.pipeline_2d_backdrop)
|
||||
destroy_pipeline_2d_base(device, &GLOB.pipeline_2d_base)
|
||||
destroy_text_cache()
|
||||
}
|
||||
@@ -373,6 +394,7 @@ clear_global :: proc() {
|
||||
clear(&GLOB.tmp_text_batches)
|
||||
clear(&GLOB.tmp_primitives)
|
||||
clear(&GLOB.tmp_sub_batches)
|
||||
clear(&GLOB.tmp_backdrop_primitives)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
@@ -465,7 +487,7 @@ prepare_shape :: proc(layer: ^Layer, vertices: []Vertex) {
|
||||
}
|
||||
|
||||
// Submit an SDF primitive to the given layer for rendering.
|
||||
prepare_sdf_primitive :: proc(layer: ^Layer, prim: Primitive) {
|
||||
prepare_sdf_primitive :: proc(layer: ^Layer, prim: Base_2D_Primitive) {
|
||||
offset := u32(len(GLOB.tmp_primitives))
|
||||
append(&GLOB.tmp_primitives, prim)
|
||||
scissor := &GLOB.scissors[layer.scissor_start + layer.scissor_len - 1]
|
||||
@@ -578,6 +600,12 @@ prepare_text_transformed :: proc(layer: ^Layer, text: Text, transform: Transform
|
||||
}
|
||||
|
||||
// Append a new sub-batch or extend the last one if same kind and contiguous.
|
||||
//
|
||||
// `gaussian_sigma` is only consulted for kind == .Backdrop; two .Backdrop sub-batches with
|
||||
// different sigmas cannot coalesce because they require separate H+V blur passes in the
|
||||
// bracket scheduler. Float equality is intentional — user-supplied literal sigmas (e.g.
|
||||
// `sigma = 12`) produce bit-identical floats, and the worst case for two sigmas that differ
|
||||
// only by a ulp is one extra pass pair (correct, just slightly suboptimal).
|
||||
@(private)
|
||||
append_or_extend_sub_batch :: proc(
|
||||
scissor: ^Scissor,
|
||||
@@ -587,6 +615,7 @@ append_or_extend_sub_batch :: proc(
|
||||
count: u32,
|
||||
texture_id: Texture_Id = INVALID_TEXTURE,
|
||||
sampler: Sampler_Preset = DFT_SAMPLER,
|
||||
gaussian_sigma: f32 = 0,
|
||||
) {
|
||||
if scissor.sub_batch_len > 0 {
|
||||
last := &GLOB.tmp_sub_batches[scissor.sub_batch_start + scissor.sub_batch_len - 1]
|
||||
@@ -594,14 +623,22 @@ append_or_extend_sub_batch :: proc(
|
||||
kind != .Text &&
|
||||
last.offset + last.count == offset &&
|
||||
last.texture_id == texture_id &&
|
||||
last.sampler == sampler {
|
||||
last.sampler == sampler &&
|
||||
(kind != .Backdrop || last.gaussian_sigma == gaussian_sigma) {
|
||||
last.count += count
|
||||
return
|
||||
}
|
||||
}
|
||||
append(
|
||||
&GLOB.tmp_sub_batches,
|
||||
Sub_Batch{kind = kind, offset = offset, count = count, texture_id = texture_id, sampler = sampler},
|
||||
Sub_Batch {
|
||||
kind = kind,
|
||||
offset = offset,
|
||||
count = count,
|
||||
texture_id = texture_id,
|
||||
sampler = sampler,
|
||||
gaussian_sigma = gaussian_sigma,
|
||||
},
|
||||
)
|
||||
scissor.sub_batch_len += 1
|
||||
layer.sub_batch_len += 1
|
||||
@@ -710,7 +747,7 @@ prepare_clay_batch :: proc(
|
||||
|
||||
// Background color behind the image (Clay allows it)
|
||||
bg := color_from_clay(render_data.backgroundColor)
|
||||
if bg[3] > 0 {
|
||||
if bg.a > 0 {
|
||||
rectangle(layer, bounds, bg, radii = radii)
|
||||
}
|
||||
|
||||
@@ -718,7 +755,12 @@ prepare_clay_batch :: proc(
|
||||
uv, sampler, inner := fit_params(img_data.fit, bounds, img_data.texture_id)
|
||||
|
||||
// Draw the image
|
||||
rectangle_texture(layer, inner, img_data.texture_id, img_data.tint, uv, sampler, radii)
|
||||
rectangle(
|
||||
layer,
|
||||
inner,
|
||||
Texture_Fill{id = img_data.texture_id, tint = img_data.tint, uv_rect = uv, sampler = sampler},
|
||||
radii = radii,
|
||||
)
|
||||
case clay.RenderCommandType.ScissorStart:
|
||||
if bounds.width == 0 || bounds.height == 0 do continue
|
||||
|
||||
@@ -787,9 +829,19 @@ end :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window, clear_color: Color = DF
|
||||
log.panicf("Failed to acquire GPU command buffer: %s", sdl.GetError())
|
||||
}
|
||||
|
||||
// Upload primitives to GPU
|
||||
// Pre-scan: if any layer this frame has a backdrop sub-batch, route the entire frame to
|
||||
// source_texture (Approach B) so the bracket can sample the pre-bracket framebuffer
|
||||
// without a mid-frame texture copy. Frames without any backdrop hit the existing fast
|
||||
// path and never touch the backdrop pipeline's working textures.
|
||||
has_backdrop := frame_has_backdrop()
|
||||
|
||||
// Upload primitives to GPU (vertices, indices, SDF prims, and backdrop prims share one
|
||||
// copy pass so we pay the BeginGPUCopyPass / EndGPUCopyPass cost once per frame).
|
||||
copy_pass := sdl.BeginGPUCopyPass(cmd_buffer)
|
||||
upload(device, copy_pass)
|
||||
if has_backdrop {
|
||||
upload_backdrop_primitives(device, copy_pass)
|
||||
}
|
||||
sdl.EndGPUCopyPass(copy_pass)
|
||||
|
||||
swapchain_texture: ^sdl.GPUTexture
|
||||
@@ -806,12 +858,10 @@ end :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window, clear_color: Color = DF
|
||||
return
|
||||
}
|
||||
|
||||
use_msaa := GLOB.sample_count != ._1
|
||||
render_texture := swapchain_texture
|
||||
|
||||
if use_msaa {
|
||||
ensure_msaa_texture(device, sdl.GetGPUSwapchainTextureFormat(device, window), width, height)
|
||||
render_texture = GLOB.msaa_texture
|
||||
if has_backdrop {
|
||||
ensure_backdrop_textures(device, sdl.GetGPUSwapchainTextureFormat(device, window), width, height)
|
||||
render_texture = GLOB.pipeline_2d_backdrop.source_texture
|
||||
}
|
||||
|
||||
// Premultiply clear color: the blend state is ONE, ONE_MINUS_SRC_ALPHA (premultiplied),
|
||||
@@ -827,24 +877,23 @@ end :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window, clear_color: Color = DF
|
||||
|
||||
// Draw layers. One render pass per layer; sub-batches draw in submission order within each scissor.
|
||||
for &layer, index in GLOB.layers {
|
||||
log.debug("Drawing layer", index)
|
||||
draw_layer(device, window, cmd_buffer, render_texture, width, height, clear_color_f32, &layer)
|
||||
}
|
||||
|
||||
// Resolve MSAA render texture to the swapchain.
|
||||
if use_msaa {
|
||||
resolve_pass := sdl.BeginGPURenderPass(
|
||||
cmd_buffer,
|
||||
&sdl.GPUColorTargetInfo {
|
||||
texture = render_texture,
|
||||
load_op = .LOAD,
|
||||
store_op = .RESOLVE,
|
||||
resolve_texture = swapchain_texture,
|
||||
},
|
||||
// Approach B finalization: when we rendered into source_texture, copy it to the swapchain.
|
||||
// Single CopyGPUTextureToTexture call per frame, only when backdrop content was present.
|
||||
if has_backdrop {
|
||||
copy_pass := sdl.BeginGPUCopyPass(cmd_buffer)
|
||||
sdl.CopyGPUTextureToTexture(
|
||||
copy_pass,
|
||||
sdl.GPUTextureLocation{texture = GLOB.pipeline_2d_backdrop.source_texture},
|
||||
sdl.GPUTextureLocation{texture = swapchain_texture},
|
||||
width,
|
||||
height,
|
||||
1,
|
||||
nil,
|
||||
false,
|
||||
)
|
||||
sdl.EndGPURenderPass(resolve_pass)
|
||||
sdl.EndGPUCopyPass(copy_pass)
|
||||
}
|
||||
|
||||
if !sdl.SubmitGPUCommandBuffer(cmd_buffer) {
|
||||
@@ -852,48 +901,6 @@ end :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window, clear_color: Color = DF
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- MSAA --------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// Query the highest MSAA sample count supported by the GPU for the swapchain format.
|
||||
max_sample_count :: proc(device: ^sdl.GPUDevice, window: ^sdl.Window) -> sdl.GPUSampleCount {
|
||||
format := sdl.GetGPUSwapchainTextureFormat(device, window)
|
||||
counts := [?]sdl.GPUSampleCount{._8, ._4, ._2}
|
||||
for count in counts {
|
||||
if sdl.GPUTextureSupportsSampleCount(device, format, count) do return count
|
||||
}
|
||||
return ._1
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
ensure_msaa_texture :: proc(device: ^sdl.GPUDevice, format: sdl.GPUTextureFormat, width, height: u32) {
|
||||
if GLOB.msaa_texture != nil && GLOB.msaa_width == width && GLOB.msaa_height == height {
|
||||
return
|
||||
}
|
||||
if GLOB.msaa_texture != nil {
|
||||
sdl.ReleaseGPUTexture(device, GLOB.msaa_texture)
|
||||
}
|
||||
GLOB.msaa_texture = sdl.CreateGPUTexture(
|
||||
device,
|
||||
sdl.GPUTextureCreateInfo {
|
||||
type = .D2,
|
||||
format = format,
|
||||
usage = {.COLOR_TARGET},
|
||||
width = width,
|
||||
height = height,
|
||||
layer_count_or_depth = 1,
|
||||
num_levels = 1,
|
||||
sample_count = GLOB.sample_count,
|
||||
},
|
||||
)
|
||||
if GLOB.msaa_texture == nil {
|
||||
log.panicf("Failed to create MSAA texture (%dx%d): %s", width, height, sdl.GetError())
|
||||
}
|
||||
GLOB.msaa_width = width
|
||||
GLOB.msaa_height = height
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Utility -----------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user