test(graphics): end-to-end bloom postprocess demo (Phase 3)
test/test-postprocess-demo/ exercises the full sigil-graphics post-processing surface end-to-end: 1. Render a scene of orbiting bright shapes into a 960×540 offscreen render target (additive blending so overlapping circles brighten toward white, plus a pulsing white core in the middle). 2. Composite the target onto the swap chain through a one-pass bloom shader. Bright pixels gain a visible glow halo via a 7×7 Gaussian-weighted blur of smoothstep-thresholded bright regions.
Caller-set uniforms: uintensity, uthreshold, uradius. Auto uniforms utime + uresolution are populated each frame by with-shader; utime also drives a subtle pulse on the bloom intensity so capture validation can see the auto-uniform pathway in motion.
Not production-quality bloom — a real pipeline would downsample to half-res first to bound the fragment cost. This is enough to prove the surface works end-to-end and to validate the cinder integration.
test/test-postprocess-demo/dev-redirects.sgl | 6 ++++++
test/test-postprocess-demo/package.sgl | 30 ++++++++++++++++++++++++++++
test/test-postprocess-demo/src/test-postprocess-demo/main.sgl | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 179 insertions(+)test/test-postprocess-demo/dev-redirects.sgladded
;; Local sigil-graphics is the in-progress version under development.(redirects repos: (list (for-repo url: "codeberg:sigil/sigil-graphics" use: (from-path dir: "../.."))))test/test-postprocess-demo/package.sgladded
;;; test-postprocess-demo — end-to-end bloom postprocess demo;;;;;; Renders a few moving bright shapes into an offscreen render target,;;; then composites the target onto the swap chain through a one-pass;;; bloom shader. Validates the full sigil-graphics post-processing;;; surface end-to-end (RT + custom shader + auto-uniforms). Quits;;; after ~5 seconds or when escape is pressed.(package name: "test-postprocess-demo" version: "0.0.1" sigil: "^0.14" description: "End-to-end bloom postprocess demo on sigil-graphics" license: "BSD-3-Clause" authors: (list "David Wilson <[email protected]>") entry: '(test-postprocess-demo main) bundle-name: "test-postprocess-demo" configs: (list (config name: 'dev output-dir: "build/dev" debug?: #t optimize: 0 bundle?: #t)) dependencies: (list (from-git url: "codeberg:sigil/sigil-app" version: "^0.8.2") (from-git url: "codeberg:sigil/sigil-graphics" version: "^0.9.3")))test/test-postprocess-demo/src/test-postprocess-demo/main.sgladded
;;; test-postprocess-demo / main — end-to-end bloom demo;;;;;; Proves the sigil-graphics post-processing surface end-to-end:;;; 1. Render scene into a 960×540 offscreen target (a few bright;;; moving circles + a dim backdrop).;;; 2. Composite the target onto the swap chain through a one-pass;;; bloom shader. Bright pixels (the circles) gain a visible glow;;; halo; dim pixels (the backdrop) pass through unmodified.;;;;;; Quits after ~5 seconds or when escape is pressed.(define-library (test-postprocess-demo main) (import (sigil core) (sigil math) (sigil app) (sigil graphics)) (export main) (begin (define WINDOW-W 960) (define WINDOW-H 540) (define VERT-SOURCE (string-append "#version 410\n" "layout(location = 0) in vec4 coord;\n" "layout(location = 1) in vec4 color;\n" "out vec2 texUV;\n" "out vec4 iColor;\n" "void main() {\n" " gl_Position = vec4(coord.xy, 0.0, 1.0);\n" " texUV = coord.zw;\n" " iColor = color;\n" "}\n")) ;; One-pass bloom: 7x7 Gaussian-weighted blur of the ;; threshold-extracted bright regions, added back on top of the ;; original scene. Threshold uses smoothstep so it doesn't ;; alias against pixel-aligned bright shapes; intensity ;; multiplies the bloom contribution so bright pixels get a ;; visible halo. Not production-quality (a real pipeline would ;; downsample to a half-res target first) but enough to prove ;; the surface works end to end. (define BLOOM-FRAG (string-append "#version 410\n" "uniform sampler2D iTexChannel0_iSmpChannel0;\n" "uniform float u_time;\n" "uniform vec2 u_resolution;\n" "uniform float u_intensity;\n" "uniform float u_threshold;\n" "uniform float u_radius;\n" "in vec2 texUV;\n" "in vec4 iColor;\n" "out vec4 fragColor;\n" "\n" "vec3 sample_bright(vec2 uv) {\n" " vec3 c = texture(iTexChannel0_iSmpChannel0, uv).rgb;\n" " float luma = max(max(c.r, c.g), c.b);\n" " return c * smoothstep(u_threshold, u_threshold + 0.15, luma);\n" "}\n" "\n" "void main() {\n" " vec3 base = texture(iTexChannel0_iSmpChannel0, texUV).rgb;\n" " vec2 px_step = u_radius / u_resolution;\n" " vec3 bloom = vec3(0.0);\n" " float wsum = 0.0;\n" " // 7x7 Gaussian-ish kernel — sigma ~ 1.5 cells.\n" " for (int j = -3; j <= 3; j++) {\n" " for (int i = -3; i <= 3; i++) {\n" " float w = exp(-float(i*i + j*j) * 0.22);\n" " bloom += sample_bright(texUV + vec2(i, j) * px_step) * w;\n" " wsum += w;\n" " }\n" " }\n" " bloom /= wsum;\n" " // Slight time-driven pulse so the auto-uniform u_time is\n" " // clearly exercised on capture.\n" " float pulse = 0.85 + 0.15 * sin(u_time * 1.7);\n" " vec3 outc = base + bloom * (u_intensity * pulse);\n" " fragColor = vec4(outc, 1.0) * iColor;\n" "}\n")) ;; Three orbiting circles in saturated colors — saturation is what ;; makes the bloom halo unambiguous. The backdrop stays dim so it ;; doesn't compete with the bloom. (define (draw-bright-scene t) (set-color 0.04 0.05 0.10 1.0) (draw-filled-rect 0.0 0.0 (inexact WINDOW-W) (inexact WINDOW-H)) (let* ((cx (* 0.5 (inexact WINDOW-W))) (cy (* 0.5 (inexact WINDOW-H))) (r 140.0)) ;; Three orbiting circles, 120° apart, drawn additively so ;; overlap regions get extra bright (= more bloom). (with-additive-blend (set-color 1.0 0.35 0.35 1.0) (draw-filled-circle (+ cx (* r (cos t))) (+ cy (* r (sin t))) 28.0 32) (set-color 0.35 1.0 0.45 1.0) (let ((a (+ t (/ (* 2.0 3.14159265) 3.0)))) (draw-filled-circle (+ cx (* r (cos a))) (+ cy (* r (sin a))) 28.0 32)) (set-color 0.40 0.55 1.0 1.0) (let ((a (+ t (/ (* 4.0 3.14159265) 3.0)))) (draw-filled-circle (+ cx (* r (cos a))) (+ cy (* r (sin a))) 28.0 32)) ;; A small, very bright white core in the middle pulsing on ;; t — gives the bloom a steady focal point. (set-color 1.0 1.0 1.0 (+ 0.6 (* 0.3 (sin (* t 2.5))))) (draw-filled-circle cx cy 16.0 32)))) (define (game-loop) (gfx-setup) (set-viewport WINDOW-W WINDOW-H) (let* ((rt (make-render-target WINDOW-W WINDOW-H)) (bloom (load-shader VERT-SOURCE BLOOM-FRAG))) (set-shader-uniform bloom 'u_intensity 1.4) (set-shader-uniform bloom 'u_threshold 0.55) (set-shader-uniform bloom 'u_radius 3.5) (let loop ((elapsed 0.0)) (let ((dt (wait-frame))) (with-render-target rt (draw-bright-scene elapsed)) (with-frame (clear-screen 0.0 0.0 0.0 1.0) (with-shader bloom (draw-render-target rt 0.0 0.0 (inexact WINDOW-W) (inexact WINDOW-H)))) (cond ((key-pressed? 'escape) (request-quit)) ((quit-requested?) #t) ((> elapsed 5.0) (request-quit)) (else (loop (+ elapsed dt)))))) (shader-free! bloom) (render-target-free! rt)) (gfx-shutdown)) (define (main . _args) (run-game "sigil-graphics bloom postprocess demo" WINDOW-W WINDOW-H game-loop) 0)))