Added backdrop effects pipeline (blur)
This commit is contained in:
@@ -0,0 +1,383 @@
|
||||
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})
|
||||
}
|
||||
}
|
||||
+66
-43
@@ -5,65 +5,88 @@ import "core:log"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
|
||||
EX_HELLOPE_SHAPES :: "hellope-shapes"
|
||||
EX_HELLOPE_TEXT :: "hellope-text"
|
||||
EX_HELLOPE_CLAY :: "hellope-clay"
|
||||
EX_HELLOPE_CUSTOM :: "hellope-custom"
|
||||
EX_TEXTURES :: "textures"
|
||||
EX_GAUSSIAN_BLUR :: "gaussian-blur"
|
||||
EX_GAUSSIAN_BLUR_DEBUG :: "gaussian-blur-debug"
|
||||
|
||||
AVAILABLE_EXAMPLES_MSG ::
|
||||
"Available examples: " +
|
||||
EX_HELLOPE_SHAPES +
|
||||
", " +
|
||||
EX_HELLOPE_TEXT +
|
||||
", " +
|
||||
EX_HELLOPE_CLAY +
|
||||
", " +
|
||||
EX_HELLOPE_CUSTOM +
|
||||
", " +
|
||||
EX_TEXTURES +
|
||||
", " +
|
||||
EX_GAUSSIAN_BLUR +
|
||||
", " +
|
||||
EX_GAUSSIAN_BLUR_DEBUG
|
||||
|
||||
main :: proc() {
|
||||
//----- General setup ----------------------------------
|
||||
{
|
||||
// Temp
|
||||
track_temp: mem.Tracking_Allocator
|
||||
mem.tracking_allocator_init(&track_temp, context.temp_allocator)
|
||||
context.temp_allocator = mem.tracking_allocator(&track_temp)
|
||||
// Temp
|
||||
track_temp: mem.Tracking_Allocator
|
||||
mem.tracking_allocator_init(&track_temp, context.temp_allocator)
|
||||
context.temp_allocator = mem.tracking_allocator(&track_temp)
|
||||
|
||||
// Default
|
||||
track: mem.Tracking_Allocator
|
||||
mem.tracking_allocator_init(&track, context.allocator)
|
||||
context.allocator = mem.tracking_allocator(&track)
|
||||
// Log a warning about any memory that was not freed by the end of the program.
|
||||
// This could be fine for some global state or it could be a memory leak.
|
||||
defer {
|
||||
// Temp allocator
|
||||
if len(track_temp.bad_free_array) > 0 {
|
||||
fmt.eprintf("=== %v incorrect frees - temp allocator: ===\n", len(track_temp.bad_free_array))
|
||||
for entry in track_temp.bad_free_array {
|
||||
fmt.eprintf("- %p @ %v\n", entry.memory, entry.location)
|
||||
}
|
||||
mem.tracking_allocator_destroy(&track_temp)
|
||||
// Default
|
||||
track: mem.Tracking_Allocator
|
||||
mem.tracking_allocator_init(&track, context.allocator)
|
||||
context.allocator = mem.tracking_allocator(&track)
|
||||
// Log a warning about any memory that was not freed by the end of the program.
|
||||
// This could be fine for some global state or it could be a memory leak.
|
||||
defer {
|
||||
// Temp allocator
|
||||
if len(track_temp.bad_free_array) > 0 {
|
||||
fmt.eprintf("=== %v incorrect frees - temp allocator: ===\n", len(track_temp.bad_free_array))
|
||||
for entry in track_temp.bad_free_array {
|
||||
fmt.eprintf("- %p @ %v\n", entry.memory, entry.location)
|
||||
}
|
||||
// Default allocator
|
||||
if len(track.allocation_map) > 0 {
|
||||
fmt.eprintf("=== %v allocations not freed - main allocator: ===\n", len(track.allocation_map))
|
||||
for _, entry in track.allocation_map {
|
||||
fmt.eprintf("- %v bytes @ %v\n", entry.size, entry.location)
|
||||
}
|
||||
}
|
||||
if len(track.bad_free_array) > 0 {
|
||||
fmt.eprintf("=== %v incorrect frees - main allocator: ===\n", len(track.bad_free_array))
|
||||
for entry in track.bad_free_array {
|
||||
fmt.eprintf("- %p @ %v\n", entry.memory, entry.location)
|
||||
}
|
||||
}
|
||||
mem.tracking_allocator_destroy(&track)
|
||||
mem.tracking_allocator_destroy(&track_temp)
|
||||
}
|
||||
// Logger
|
||||
context.logger = log.create_console_logger()
|
||||
defer log.destroy_console_logger(context.logger)
|
||||
// Default allocator
|
||||
if len(track.allocation_map) > 0 {
|
||||
fmt.eprintf("=== %v allocations not freed - main allocator: ===\n", len(track.allocation_map))
|
||||
for _, entry in track.allocation_map {
|
||||
fmt.eprintf("- %v bytes @ %v\n", entry.size, entry.location)
|
||||
}
|
||||
}
|
||||
if len(track.bad_free_array) > 0 {
|
||||
fmt.eprintf("=== %v incorrect frees - main allocator: ===\n", len(track.bad_free_array))
|
||||
for entry in track.bad_free_array {
|
||||
fmt.eprintf("- %p @ %v\n", entry.memory, entry.location)
|
||||
}
|
||||
}
|
||||
mem.tracking_allocator_destroy(&track)
|
||||
}
|
||||
context.logger = log.create_console_logger()
|
||||
defer log.destroy_console_logger(context.logger)
|
||||
|
||||
args := os.args
|
||||
if len(args) < 2 {
|
||||
fmt.eprintln("Usage: examples <example_name>")
|
||||
fmt.eprintln("Available examples: hellope-shapes, hellope-text, hellope-clay, hellope-custom, textures")
|
||||
fmt.eprintln(AVAILABLE_EXAMPLES_MSG)
|
||||
os.exit(1)
|
||||
}
|
||||
|
||||
switch args[1] {
|
||||
case "hellope-clay": hellope_clay()
|
||||
case "hellope-custom": hellope_custom()
|
||||
case "hellope-shapes": hellope_shapes()
|
||||
case "hellope-text": hellope_text()
|
||||
case "textures": textures()
|
||||
case EX_HELLOPE_CLAY: hellope_clay()
|
||||
case EX_HELLOPE_CUSTOM: hellope_custom()
|
||||
case EX_HELLOPE_SHAPES: hellope_shapes()
|
||||
case EX_HELLOPE_TEXT: hellope_text()
|
||||
case EX_TEXTURES: textures()
|
||||
case EX_GAUSSIAN_BLUR: gaussian_blur()
|
||||
case EX_GAUSSIAN_BLUR_DEBUG: gaussian_blur_debug()
|
||||
case:
|
||||
fmt.eprintf("Unknown example: %v\n", args[1])
|
||||
fmt.eprintln("Available examples: hellope-shapes, hellope-text, hellope-clay, hellope-custom, textures")
|
||||
fmt.eprintln(AVAILABLE_EXAMPLES_MSG)
|
||||
os.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,8 +48,7 @@ hellope_shapes :: proc() {
|
||||
draw.rectangle(
|
||||
base_layer,
|
||||
{20, 160, 460, 60},
|
||||
{255, 0, 0, 255},
|
||||
gradient = draw.Linear_Gradient{end_color = {0, 0, 255, 255}, angle = 0},
|
||||
draw.Linear_Gradient{start_color = {255, 0, 0, 255}, end_color = {0, 0, 255, 255}, angle = 0},
|
||||
)
|
||||
|
||||
// ----- Rotation demos -----
|
||||
@@ -79,18 +78,18 @@ hellope_shapes :: proc() {
|
||||
)
|
||||
|
||||
// Ellipse rotating around its center (tilted ellipse)
|
||||
draw.ellipse(base_layer, {410, 340}, 50, 30, {255, 200, 50, 255}, rotation = spin_angle)
|
||||
draw.ellipse(base_layer, {410, 340}, 50, 30, draw.Color{255, 200, 50, 255}, rotation = spin_angle)
|
||||
|
||||
// Circle orbiting a point (moon orbiting planet)
|
||||
// Convention B: center = pivot point (planet), origin = offset from moon center to pivot.
|
||||
// Moon's visual center at rotation=0: planet_pos - origin = (100, 450) - (0, 40) = (100, 410).
|
||||
planet_pos := draw.Vec2{100, 450}
|
||||
draw.circle(base_layer, planet_pos, 8, {200, 200, 200, 255}) // planet (stationary)
|
||||
draw.circle(base_layer, planet_pos, 8, draw.Color{200, 200, 200, 255}) // planet (stationary)
|
||||
draw.circle(
|
||||
base_layer,
|
||||
planet_pos,
|
||||
5,
|
||||
{100, 150, 255, 255},
|
||||
draw.Color{100, 150, 255, 255},
|
||||
origin = draw.Vec2{0, 40},
|
||||
rotation = spin_angle,
|
||||
) // moon orbiting
|
||||
@@ -101,7 +100,7 @@ hellope_shapes :: proc() {
|
||||
draw.Vec2{250, 450},
|
||||
0,
|
||||
30,
|
||||
{100, 100, 220, 255},
|
||||
draw.Color{100, 100, 220, 255},
|
||||
start_angle = 0,
|
||||
end_angle = 270,
|
||||
rotation = spin_angle,
|
||||
@@ -127,7 +126,7 @@ hellope_shapes :: proc() {
|
||||
{460, 450},
|
||||
6,
|
||||
30,
|
||||
{180, 100, 220, 255},
|
||||
draw.Color{180, 100, 220, 255},
|
||||
outline_color = draw.WHITE,
|
||||
outline_width = 2,
|
||||
rotation = spin_angle,
|
||||
@@ -190,14 +189,7 @@ hellope_text :: proc() {
|
||||
)
|
||||
|
||||
// Uncached text (no id) — created and destroyed each frame, simplest usage
|
||||
draw.text(
|
||||
base_layer,
|
||||
"Top-left anchored",
|
||||
{20, 450},
|
||||
PLEX_SANS_REGULAR,
|
||||
FONT_SIZE,
|
||||
color = draw.WHITE,
|
||||
)
|
||||
draw.text(base_layer, "Top-left anchored", {20, 450}, PLEX_SANS_REGULAR, FONT_SIZE, color = draw.WHITE)
|
||||
|
||||
// Measure text for manual layout
|
||||
size := draw.measure_text("Measured!", PLEX_SANS_REGULAR, FONT_SIZE)
|
||||
|
||||
+164
-28
@@ -9,7 +9,7 @@ import cyber "../cybersteel"
|
||||
|
||||
textures :: proc() {
|
||||
if !sdl.Init({.VIDEO}) do os.exit(1)
|
||||
window := sdl.CreateWindow("Textures", 800, 600, {.HIGH_PIXEL_DENSITY})
|
||||
window := sdl.CreateWindow("Textures", 800, 750, {.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)
|
||||
@@ -88,10 +88,10 @@ textures :: proc() {
|
||||
}
|
||||
spin_angle += 1
|
||||
|
||||
base_layer := draw.begin({width = 800, height = 600})
|
||||
base_layer := draw.begin({width = 800, height = 750})
|
||||
|
||||
// Background
|
||||
draw.rectangle(base_layer, {0, 0, 800, 600}, draw.Color{30, 30, 30, 255})
|
||||
draw.rectangle(base_layer, {0, 0, 800, 750}, draw.Color{30, 30, 30, 255})
|
||||
|
||||
//----- Row 1: Sampler presets (y=30) ----------------------------------
|
||||
|
||||
@@ -103,11 +103,15 @@ textures :: proc() {
|
||||
COL4 :: f32(480)
|
||||
|
||||
// Nearest (sharp pixel edges)
|
||||
draw.rectangle_texture(
|
||||
draw.rectangle(
|
||||
base_layer,
|
||||
{COL1, ROW1_Y, ITEM_SIZE, ITEM_SIZE},
|
||||
checker_texture,
|
||||
sampler = .Nearest_Clamp,
|
||||
draw.Texture_Fill {
|
||||
id = checker_texture,
|
||||
tint = draw.WHITE,
|
||||
uv_rect = {0, 0, 1, 1},
|
||||
sampler = .Nearest_Clamp,
|
||||
},
|
||||
)
|
||||
draw.text(
|
||||
base_layer,
|
||||
@@ -119,11 +123,15 @@ textures :: proc() {
|
||||
)
|
||||
|
||||
// Linear (bilinear blur)
|
||||
draw.rectangle_texture(
|
||||
draw.rectangle(
|
||||
base_layer,
|
||||
{COL2, ROW1_Y, ITEM_SIZE, ITEM_SIZE},
|
||||
checker_texture,
|
||||
sampler = .Linear_Clamp,
|
||||
draw.Texture_Fill {
|
||||
id = checker_texture,
|
||||
tint = draw.WHITE,
|
||||
uv_rect = {0, 0, 1, 1},
|
||||
sampler = .Linear_Clamp,
|
||||
},
|
||||
)
|
||||
draw.text(
|
||||
base_layer,
|
||||
@@ -135,12 +143,15 @@ textures :: proc() {
|
||||
)
|
||||
|
||||
// Tiled (4x repeat)
|
||||
draw.rectangle_texture(
|
||||
draw.rectangle(
|
||||
base_layer,
|
||||
{COL3, ROW1_Y, ITEM_SIZE, ITEM_SIZE},
|
||||
checker_texture,
|
||||
sampler = .Nearest_Repeat,
|
||||
uv_rect = {0, 0, 4, 4},
|
||||
draw.Texture_Fill {
|
||||
id = checker_texture,
|
||||
tint = draw.WHITE,
|
||||
uv_rect = {0, 0, 4, 4},
|
||||
sampler = .Nearest_Repeat,
|
||||
},
|
||||
)
|
||||
draw.text(
|
||||
base_layer,
|
||||
@@ -157,11 +168,10 @@ textures :: proc() {
|
||||
|
||||
// QR code (RGBA texture with baked colors, nearest sampling)
|
||||
draw.rectangle(base_layer, {COL1, ROW2_Y, ITEM_SIZE, ITEM_SIZE}, draw.Color{255, 255, 255, 255}) // white bg
|
||||
draw.rectangle_texture(
|
||||
draw.rectangle(
|
||||
base_layer,
|
||||
{COL1, ROW2_Y, ITEM_SIZE, ITEM_SIZE},
|
||||
qr_texture,
|
||||
sampler = .Nearest_Clamp,
|
||||
draw.Texture_Fill{id = qr_texture, tint = draw.WHITE, uv_rect = {0, 0, 1, 1}, sampler = .Nearest_Clamp},
|
||||
)
|
||||
draw.text(
|
||||
base_layer,
|
||||
@@ -173,11 +183,15 @@ textures :: proc() {
|
||||
)
|
||||
|
||||
// Rounded corners
|
||||
draw.rectangle_texture(
|
||||
draw.rectangle(
|
||||
base_layer,
|
||||
{COL2, ROW2_Y, ITEM_SIZE, ITEM_SIZE},
|
||||
checker_texture,
|
||||
sampler = .Nearest_Clamp,
|
||||
draw.Texture_Fill {
|
||||
id = checker_texture,
|
||||
tint = draw.WHITE,
|
||||
uv_rect = {0, 0, 1, 1},
|
||||
sampler = .Nearest_Clamp,
|
||||
},
|
||||
radii = draw.uniform_radii({COL2, ROW2_Y, ITEM_SIZE, ITEM_SIZE}, 0.3),
|
||||
)
|
||||
draw.text(
|
||||
@@ -191,11 +205,15 @@ textures :: proc() {
|
||||
|
||||
// Rotating
|
||||
rot_rect := draw.Rectangle{COL3, ROW2_Y, ITEM_SIZE, ITEM_SIZE}
|
||||
draw.rectangle_texture(
|
||||
draw.rectangle(
|
||||
base_layer,
|
||||
rot_rect,
|
||||
checker_texture,
|
||||
sampler = .Nearest_Clamp,
|
||||
draw.Texture_Fill {
|
||||
id = checker_texture,
|
||||
tint = draw.WHITE,
|
||||
uv_rect = {0, 0, 1, 1},
|
||||
sampler = .Nearest_Clamp,
|
||||
},
|
||||
origin = draw.center_of(rot_rect),
|
||||
rotation = spin_angle,
|
||||
)
|
||||
@@ -216,7 +234,11 @@ textures :: proc() {
|
||||
// Stretch
|
||||
uv_s, sampler_s, inner_s := draw.fit_params(.Stretch, {COL1, ROW3_Y, FIT_SIZE, FIT_SIZE}, stripe_texture)
|
||||
draw.rectangle(base_layer, {COL1, ROW3_Y, FIT_SIZE, FIT_SIZE}, draw.Color{60, 60, 60, 255}) // bg
|
||||
draw.rectangle_texture(base_layer, inner_s, stripe_texture, uv_rect = uv_s, sampler = sampler_s)
|
||||
draw.rectangle(
|
||||
base_layer,
|
||||
inner_s,
|
||||
draw.Texture_Fill{id = stripe_texture, tint = draw.WHITE, uv_rect = uv_s, sampler = sampler_s},
|
||||
)
|
||||
draw.text(
|
||||
base_layer,
|
||||
"Stretch",
|
||||
@@ -229,7 +251,11 @@ textures :: proc() {
|
||||
// Fill (center-crop)
|
||||
uv_f, sampler_f, inner_f := draw.fit_params(.Fill, {COL2, ROW3_Y, FIT_SIZE, FIT_SIZE}, stripe_texture)
|
||||
draw.rectangle(base_layer, {COL2, ROW3_Y, FIT_SIZE, FIT_SIZE}, draw.Color{60, 60, 60, 255})
|
||||
draw.rectangle_texture(base_layer, inner_f, stripe_texture, uv_rect = uv_f, sampler = sampler_f)
|
||||
draw.rectangle(
|
||||
base_layer,
|
||||
inner_f,
|
||||
draw.Texture_Fill{id = stripe_texture, tint = draw.WHITE, uv_rect = uv_f, sampler = sampler_f},
|
||||
)
|
||||
draw.text(
|
||||
base_layer,
|
||||
"Fill",
|
||||
@@ -242,7 +268,11 @@ textures :: proc() {
|
||||
// Fit (letterbox)
|
||||
uv_ft, sampler_ft, inner_ft := draw.fit_params(.Fit, {COL3, ROW3_Y, FIT_SIZE, FIT_SIZE}, stripe_texture)
|
||||
draw.rectangle(base_layer, {COL3, ROW3_Y, FIT_SIZE, FIT_SIZE}, draw.Color{60, 60, 60, 255}) // visible margin bg
|
||||
draw.rectangle_texture(base_layer, inner_ft, stripe_texture, uv_rect = uv_ft, sampler = sampler_ft)
|
||||
draw.rectangle(
|
||||
base_layer,
|
||||
inner_ft,
|
||||
draw.Texture_Fill{id = stripe_texture, tint = draw.WHITE, uv_rect = uv_ft, sampler = sampler_ft},
|
||||
)
|
||||
draw.text(
|
||||
base_layer,
|
||||
"Fit",
|
||||
@@ -253,11 +283,15 @@ textures :: proc() {
|
||||
)
|
||||
|
||||
// Per-corner radii
|
||||
draw.rectangle_texture(
|
||||
draw.rectangle(
|
||||
base_layer,
|
||||
{COL4, ROW3_Y, FIT_SIZE, FIT_SIZE},
|
||||
checker_texture,
|
||||
sampler = .Nearest_Clamp,
|
||||
draw.Texture_Fill {
|
||||
id = checker_texture,
|
||||
tint = draw.WHITE,
|
||||
uv_rect = {0, 0, 1, 1},
|
||||
sampler = .Nearest_Clamp,
|
||||
},
|
||||
radii = {20, 0, 20, 0},
|
||||
)
|
||||
draw.text(
|
||||
@@ -269,6 +303,108 @@ textures :: proc() {
|
||||
color = draw.WHITE,
|
||||
)
|
||||
|
||||
//----- Row 4: Textured shapes (y=520) ----------------------------------
|
||||
|
||||
ROW4_Y :: f32(520)
|
||||
SHAPE_SIZE :: f32(80)
|
||||
SHAPE_GAP :: f32(30)
|
||||
SHAPE_COL1 :: f32(30)
|
||||
SHAPE_COL2 :: SHAPE_COL1 + SHAPE_SIZE + SHAPE_GAP
|
||||
SHAPE_COL3 :: SHAPE_COL2 + SHAPE_SIZE + SHAPE_GAP
|
||||
SHAPE_COL4 :: SHAPE_COL3 + SHAPE_SIZE + SHAPE_GAP
|
||||
SHAPE_COL5 :: SHAPE_COL4 + SHAPE_SIZE + SHAPE_GAP
|
||||
|
||||
checker_fill := draw.Texture_Fill {
|
||||
id = checker_texture,
|
||||
tint = draw.WHITE,
|
||||
uv_rect = {0, 0, 1, 1},
|
||||
sampler = .Nearest_Clamp,
|
||||
}
|
||||
|
||||
// Textured circle
|
||||
draw.circle(
|
||||
base_layer,
|
||||
{SHAPE_COL1 + SHAPE_SIZE / 2, ROW4_Y + SHAPE_SIZE / 2},
|
||||
SHAPE_SIZE / 2,
|
||||
checker_fill,
|
||||
)
|
||||
draw.text(
|
||||
base_layer,
|
||||
"Circle",
|
||||
{SHAPE_COL1, ROW4_Y + SHAPE_SIZE + LABEL_OFFSET},
|
||||
PLEX_SANS_REGULAR,
|
||||
FONT_SIZE,
|
||||
color = draw.WHITE,
|
||||
)
|
||||
|
||||
// Textured ellipse
|
||||
draw.ellipse(
|
||||
base_layer,
|
||||
{SHAPE_COL2 + SHAPE_SIZE / 2, ROW4_Y + SHAPE_SIZE / 2},
|
||||
SHAPE_SIZE / 2,
|
||||
SHAPE_SIZE / 3,
|
||||
checker_fill,
|
||||
)
|
||||
draw.text(
|
||||
base_layer,
|
||||
"Ellipse",
|
||||
{SHAPE_COL2, ROW4_Y + SHAPE_SIZE + LABEL_OFFSET},
|
||||
PLEX_SANS_REGULAR,
|
||||
FONT_SIZE,
|
||||
color = draw.WHITE,
|
||||
)
|
||||
|
||||
// Textured polygon (hexagon)
|
||||
draw.polygon(
|
||||
base_layer,
|
||||
{SHAPE_COL3 + SHAPE_SIZE / 2, ROW4_Y + SHAPE_SIZE / 2},
|
||||
6,
|
||||
SHAPE_SIZE / 2,
|
||||
checker_fill,
|
||||
)
|
||||
draw.text(
|
||||
base_layer,
|
||||
"Polygon",
|
||||
{SHAPE_COL3, ROW4_Y + SHAPE_SIZE + LABEL_OFFSET},
|
||||
PLEX_SANS_REGULAR,
|
||||
FONT_SIZE,
|
||||
color = draw.WHITE,
|
||||
)
|
||||
|
||||
// Textured ring
|
||||
draw.ring(
|
||||
base_layer,
|
||||
{SHAPE_COL4 + SHAPE_SIZE / 2, ROW4_Y + SHAPE_SIZE / 2},
|
||||
SHAPE_SIZE / 4,
|
||||
SHAPE_SIZE / 2,
|
||||
checker_fill,
|
||||
)
|
||||
draw.text(
|
||||
base_layer,
|
||||
"Ring",
|
||||
{SHAPE_COL4, ROW4_Y + SHAPE_SIZE + LABEL_OFFSET},
|
||||
PLEX_SANS_REGULAR,
|
||||
FONT_SIZE,
|
||||
color = draw.WHITE,
|
||||
)
|
||||
|
||||
// Textured line (capsule)
|
||||
draw.line(
|
||||
base_layer,
|
||||
{SHAPE_COL5, ROW4_Y + SHAPE_SIZE / 2},
|
||||
{SHAPE_COL5 + SHAPE_SIZE, ROW4_Y + SHAPE_SIZE / 2},
|
||||
checker_fill,
|
||||
thickness = 20,
|
||||
)
|
||||
draw.text(
|
||||
base_layer,
|
||||
"Line",
|
||||
{SHAPE_COL5, ROW4_Y + SHAPE_SIZE + LABEL_OFFSET},
|
||||
PLEX_SANS_REGULAR,
|
||||
FONT_SIZE,
|
||||
color = draw.WHITE,
|
||||
)
|
||||
|
||||
draw.end(gpu, window)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user