package examples import "core:fmt" import "core:math" import "core:os" import sdl "vendor:sdl3" import "../../draw" import cyber "../cybersteel" // Backdrop example. // // Verifies the Stage D bracket scheduler end-to-end. The demo is structured as three zones in // one window so we can stress-test the cases that matter: // // Zone 1 (top, base layer): animated colorful background + two side-by-side frosted panels // with DIFFERENT sigmas and DIFFERENT tints. Tests sigma grouping // and per-primitive tint. // // Zone 2 (bottom-left, second layer): a small frosted panel in a NEW layer; its bracket sees // Zone 1's full content (base layer's bracket output is // carried forward via source_texture). Tests multi-layer // backdrop sampling. // // Zone 3 (bottom-right, base layer): edge cases. A sigma=0 "mirror" panel (no blur), two // same-sigma panels stacked (tests sub-batch coalescing // via append_or_extend_sub_batch), and text drawn ON TOP // of a backdrop (tests Pass B post-bracket rendering). // // Animation: an orbiting gradient stripe plus a few orbiting circles in Zone 1. Motion is the // only way to visually confirm the blur is Gaussian; a static panel can't tell you whether the // kernel coefficients are right. gaussian_blur :: proc() { if !sdl.Init({.VIDEO}) do os.exit(1) window := sdl.CreateWindow("Backdrop blur", 800, 600, {.HIGH_PIXEL_DENSITY}) gpu := sdl.CreateGPUDevice(draw.PLATFORM_SHADER_FORMAT, true, nil) if !sdl.ClaimWindowForGPUDevice(gpu, window) do os.exit(1) if !draw.init(gpu, window) do os.exit(1) PLEX_SANS_REGULAR = draw.register_font(cyber.SANS_REGULAR_RAW) WINDOW_W :: f32(800) WINDOW_H :: f32(600) FONT_SIZE :: u16(14) t: f32 = 0 for { defer free_all(context.temp_allocator) ev: sdl.Event for sdl.PollEvent(&ev) { if ev.type == .QUIT do return } t += 1 base_layer := draw.begin({width = WINDOW_W, height = WINDOW_H}) //----- Background fill ---------------------------------- draw.rectangle(base_layer, {0, 0, WINDOW_W, WINDOW_H}, draw.Color{20, 20, 28, 255}) //----- Zone 1: animated background for the top frosted panels ---------------------------------- // A wide rotating gradient stripe sweeps left-to-right across Zone 1. The angle changes // over time so the gradient itself shifts visibly. stripe_angle := t * 0.4 draw.rectangle( base_layer, {20, 20, WINDOW_W - 40, 240}, draw.Linear_Gradient { start_color = {255, 80, 60, 255}, end_color = {60, 120, 255, 255}, angle = stripe_angle, }, ) // Five orbiting circles inside Zone 1's strip. The blur should smooth their hard edges // and the gradient behind them into a continuous wash. for i in 0 ..< 5 { phase := f32(i) * 1.2 + t * 0.04 cx := 100 + f32(i) * 140 + math.cos(phase) * 30 cy := 140 + math.sin(phase) * 50 circle_color := draw.Color { u8(clamp(120 + math.cos(phase) * 100, 0, 255)), u8(clamp(180 + math.sin(phase * 1.3) * 60, 0, 255)), u8(clamp(220 - math.sin(phase) * 80, 0, 255)), 255, } draw.circle(base_layer, {cx, cy}, 22, circle_color) } // Bright accent rectangles to give the blur some sharp edges to munch on. draw.rectangle(base_layer, {200, 60, 60, 12}, draw.Color{255, 255, 200, 255}) draw.rectangle(base_layer, {500, 200, 80, 16}, draw.Color{200, 255, 200, 255}) //----- Zone 1 frosted panels: different sigmas, different tints -------------------------------- // Panel A: heavy blur, cool blue-grey tint. sigma=14 in logical px. // Both panels share rounded corners. panel_radii := draw.Rectangle_Radii{16, 16, 16, 16} draw.gaussian_blur( base_layer, {60, 80, 320, 140}, gaussian_sigma = 30, tint = draw.Color{170, 200, 240, 200}, // cool blue, strong mix radii = panel_radii, ) draw.text( base_layer, "sigma = 20, cool tint", {72, 90}, PLEX_SANS_REGULAR, FONT_SIZE, color = draw.Color{30, 35, 50, 255}, ) // Panel B: lighter blur, warm amber tint. sigma=6. draw.gaussian_blur( base_layer, {420, 80, 320, 140}, gaussian_sigma = 6, tint = draw.Color{255, 220, 160, 200}, // warm amber, strong mix radii = panel_radii, ) draw.text( base_layer, "sigma = 6, warm tint", {432, 90}, PLEX_SANS_REGULAR, FONT_SIZE, color = draw.Color{60, 40, 20, 255}, ) // Pass-B verification: a rectangle drawn AFTER the backdrops in the same layer // Per the bracket scheduling model, this should render ON TOP of both panels above. // If you see this stripe behind the panels instead of in front, something is wrong with // the Pass B post-bracket path. draw.rectangle(base_layer, {WINDOW_W * 0.5 - 4, 70, 8, 160}, draw.Color{255, 255, 255, 230}) //----- Zone 2: second layer with its own backdrop -------------------------------- // Zone 2's panel is in a NEW layer. Its bracket samples source_texture as it stands // after the base layer fully finished (including the base layer's bracket V-composite // output). So this panel sees Zone 1's frosted panels through its own blur. zone2 := draw.new_layer(base_layer, {0, 280, WINDOW_W * 0.55, WINDOW_H - 280}) // Pass A content for zone2: a translucent darker overlay to make the panel pop. draw.rectangle(zone2, {20, 300, WINDOW_W * 0.55 - 40, WINDOW_H - 320}, draw.Color{0, 0, 0, 80}) // Animated diagonal stripe in Zone 2 so the blur in this layer's panel has motion to // smooth, not just the static base-layer content. stripe_y := 320 + (math.sin(t * 0.05) * 0.5 + 0.5) * 200 draw.rectangle(zone2, {30, stripe_y, WINDOW_W * 0.55 - 60, 18}, draw.Color{255, 100, 200, 200}) // Zone 2's frosted panel. draw.gaussian_blur( zone2, {60, 360, WINDOW_W * 0.55 - 120, 160}, gaussian_sigma = 10, tint = draw.WHITE, // pure blur (white tint with any alpha is a no-op) radii = draw.Rectangle_Radii{24, 24, 24, 24}, ) draw.text( zone2, "Layer 2 backdrop", {72, 372}, PLEX_SANS_REGULAR, FONT_SIZE, color = draw.Color{30, 30, 30, 255}, ) draw.text( zone2, "sigma = 10", {72, 392}, PLEX_SANS_REGULAR, FONT_SIZE, color = draw.Color{60, 60, 60, 255}, ) //----- Zone 3: edge cases (back in base layer would also work, but we use zone2 to keep -------- // the demo's two-layer structure simple). Zone 3 lives in a third layer so it gets // a fresh source snapshot too. zone3 := draw.new_layer(zone2, {WINDOW_W * 0.55, 280, WINDOW_W * 0.45, WINDOW_H - 280}) // Animated background patch for Zone 3 so its mirror panel has something to reflect. for i in 0 ..< 4 { phase := f32(i) * 1.5 + t * 0.06 y := 310 + f32(i) * 60 + math.sin(phase) * 8 draw.rectangle( zone3, {WINDOW_W * 0.55 + 20, y, WINDOW_W * 0.45 - 40, 14}, draw.Color { u8(clamp(200 + math.cos(phase) * 50, 0, 255)), u8(clamp(150 + math.sin(phase) * 80, 0, 255)), u8(clamp(220 - math.cos(phase * 1.7) * 60, 0, 255)), 255, }, ) } // Edge case 1: sigma = 0 "mirror" — sharp framebuffer sample, no blur. Should reproduce // the underlying pixels exactly through the SDF mask. Tinted slightly so it's visible. draw.gaussian_blur( zone3, {WINDOW_W * 0.55 + 30, 310, 150, 70}, gaussian_sigma = 0, tint = draw.WHITE, // pure mirror (no blur, no tint) radii = draw.Rectangle_Radii{12, 12, 12, 12}, ) draw.text( zone3, "sigma=0 (mirror)", {WINDOW_W * 0.55 + 38, 318}, PLEX_SANS_REGULAR, FONT_SIZE, color = draw.Color{20, 20, 20, 255}, ) // Edge case 2: two same-sigma panels submitted contiguously. The sub-batch coalescer // should merge these into a single instanced V-composite draw. Visually, both should // look identical (modulo position) — same blur radius, same tint. draw.gaussian_blur( zone3, {WINDOW_W * 0.55 + 30, 400, 150, 70}, gaussian_sigma = 8, tint = draw.Color{160, 255, 160, 200}, // green tint, strong mix radii = draw.Rectangle_Radii{12, 12, 12, 12}, ) draw.gaussian_blur( zone3, {WINDOW_W * 0.55 + 200, 400, 150, 70}, gaussian_sigma = 8, tint = draw.Color{160, 255, 160, 200}, // identical: tests sub-batch coalescing radii = draw.Rectangle_Radii{12, 12, 12, 12}, ) draw.text( zone3, "sigma=8 (coalesced pair)", {WINDOW_W * 0.55 + 38, 408}, PLEX_SANS_REGULAR, FONT_SIZE, color = draw.Color{20, 40, 20, 255}, ) // Edge case 3: text drawn AFTER a backdrop in the same layer. Tests Pass B over a fresh // V-composite output. The text should appear sharply on top of the green panels above. draw.text( zone3, "Pass B text overlay", {WINDOW_W * 0.55 + 38, 480}, PLEX_SANS_REGULAR, FONT_SIZE, color = draw.WHITE, ) draw.end(gpu, window, draw.Color{15, 15, 22, 255}) } } // Backdrop diagnostic example. // // Minimal isolation harness for debugging the blur. ONE panel, ONE sigma, NO animation. The // fixed background gives the eye a stable reference: the blur should smooth a *known* set of // hard edges, and any artifacts (crisp circles, ghost mirrors, no apparent change with sigma) // stand out clearly. // // Controls: // UP / DOWN arrow : adjust sigma by ±1 // LEFT / RIGHT arrow : adjust sigma by ±5 // SPACE : reset to sigma=10 // T : toggle the test rectangle on top of the panel // // Sigma is printed to the console label and to the title bar so you can correlate visual // behavior with kernel state (which is also logged via the [backdrop] debug print in // backdrop.odin's compute_blur_kernel callsite). gaussian_blur_debug :: proc() { if !sdl.Init({.VIDEO}) do os.exit(1) window := sdl.CreateWindow("Backdrop debug", 800, 600, {.HIGH_PIXEL_DENSITY}) gpu := sdl.CreateGPUDevice(draw.PLATFORM_SHADER_FORMAT, true, nil) if !sdl.ClaimWindowForGPUDevice(gpu, window) do os.exit(1) if !draw.init(gpu, window) do os.exit(1) defer draw.destroy(gpu) PLEX_SANS_REGULAR = draw.register_font(cyber.SANS_REGULAR_RAW) WINDOW_W :: f32(800) WINDOW_H :: f32(600) FONT_SIZE :: u16(14) sigma: f32 = 10 show_test_rect := true for { defer free_all(context.temp_allocator) ev: sdl.Event for sdl.PollEvent(&ev) { if ev.type == .QUIT do return if ev.type == .KEY_DOWN { #partial switch ev.key.scancode { case .UP: sigma += 1 case .DOWN: sigma = max(sigma - 1, 0) case .RIGHT: sigma += 5 case .LEFT: sigma = max(sigma - 5, 0) case .SPACE: sigma = 10 case .T: show_test_rect = !show_test_rect } } } // Update title with current sigma so we can correlate visuals to numbers. title := fmt.ctprintf("Backdrop debug | sigma = %.1f", sigma) sdl.SetWindowTitle(window, title) base_layer := draw.begin({width = WINDOW_W, height = WINDOW_H}) // Background: deliberately high-contrast static content. The eye can verify whether // hard edges (the black grid lines, the crisp circles, the fine vertical bars) get // smoothed by the panel. NOTHING animates here — every difference between frames is // caused by user input (sigma change), not by the demo itself. draw.rectangle(base_layer, {0, 0, WINDOW_W, WINDOW_H}, draw.Color{255, 255, 255, 255}) // Black grid: 8x6 cells with thin lines. Each grid cell is 100x100 logical px. for x: f32 = 0; x <= WINDOW_W; x += 100 { draw.rectangle(base_layer, {x - 1, 0, 2, WINDOW_H}, draw.BLACK) } for y: f32 = 0; y <= WINDOW_H; y += 100 { draw.rectangle(base_layer, {0, y - 1, WINDOW_W, 2}, draw.BLACK) } // A row of small bright circles across the middle. Their crisp edges are the most // sensitive blur indicator. for i in 0 ..< 8 { cx := f32(i) * 100 + 50 color := draw.Color{u8((i * 32) & 0xff), u8((i * 64) & 0xff), u8(255 - (i * 32) & 0xff), 255} draw.circle(base_layer, {cx, 350}, 25, color) } // Vertical fine-detail stripes on the left edge. At any meaningful sigma these should // merge into a flat color through the panel. for i in 0 ..< 20 { x := 30 + f32(i) * 6 color := draw.RED if i % 2 == 0 else draw.BLUE draw.rectangle(base_layer, {x, 200, 4, 200}, color) } // THE PANEL UNDER TEST. Square, centered, large enough to cover multiple grid cells and // the circle row. Square shape makes any horizontal-vs-vertical asymmetry purely // renderer-driven (geometry can't introduce it). panel := draw.Rectangle{250, 150, 300, 300} draw.gaussian_blur( base_layer, panel, gaussian_sigma = sigma, tint = draw.WHITE, radii = draw.Rectangle_Radii{20, 20, 20, 20}, ) // Pass B test: a bright rectangle drawn AFTER the backdrop in the same layer. Should // always render on top of the panel. If the panel ever shows a "ghost" of this rect // inside its blur, the V-composite is sampling the wrong texture state. if show_test_rect { draw.rectangle(base_layer, {380, 280, 40, 40}, draw.Color{0, 200, 0, 255}) } // Sigma label at the bottom in giant text so you can read it from across the room. draw.text( base_layer, fmt.tprintf("sigma = %.1f", sigma), {20, WINDOW_H - 40}, PLEX_SANS_REGULAR, 28, color = draw.BLACK, ) draw.text( base_layer, "UP/DOWN ±1 LEFT/RIGHT ±5 SPACE reset T toggle test rect", {20, WINDOW_H - 70}, PLEX_SANS_REGULAR, FONT_SIZE, color = draw.Color{60, 60, 60, 255}, ) draw.end(gpu, window, draw.Color{255, 255, 255, 255}) } }