Commitc5e35db5Recorded26 Apr 2026Repositorysigil-graphics

test(graphics): end-to-end bloom postprocess demo (Phase 3)

Message

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.

Changed
 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(+)
Diff
test/test-postprocess-demo/dev-redirects.sgladded
@@ -0,0 +1,6 @@
+1
;; Local sigil-graphics is the in-progress version under development.
+2
(redirects
+3
repos: (list
+4
(for-repo
+5
url: "codeberg:sigil/sigil-graphics"
+6
use: (from-path dir: "../.."))))
test/test-postprocess-demo/package.sgladded
@@ -0,0 +1,30 @@
+1
;;; test-postprocess-demo — end-to-end bloom postprocess demo
+2
;;;
+3
;;; Renders a few moving bright shapes into an offscreen render target,
+4
;;; then composites the target onto the swap chain through a one-pass
+5
;;; bloom shader. Validates the full sigil-graphics post-processing
+6
;;; surface end-to-end (RT + custom shader + auto-uniforms). Quits
+7
;;; after ~5 seconds or when escape is pressed.
+8
+9
(package
+10
name: "test-postprocess-demo"
+11
version: "0.0.1"
+12
sigil: "^0.14"
+13
description: "End-to-end bloom postprocess demo on sigil-graphics"
+14
license: "BSD-3-Clause"
+15
authors: (list "David Wilson <[email protected]>")
+16
+17
entry: '(test-postprocess-demo main)
+18
bundle-name: "test-postprocess-demo"
+19
+20
configs: (list
+21
(config
+22
name: 'dev
+23
output-dir: "build/dev"
+24
debug?: #t
+25
optimize: 0
+26
bundle?: #t))
+27
+28
dependencies: (list
+29
(from-git url: "codeberg:sigil/sigil-app" version: "^0.8.2")
+30
(from-git url: "codeberg:sigil/sigil-graphics" version: "^0.9.3")))
test/test-postprocess-demo/src/test-postprocess-demo/main.sgladded
@@ -0,0 +1,143 @@
+1
;;; test-postprocess-demo / main — end-to-end bloom demo
+2
;;;
+3
;;; Proves the sigil-graphics post-processing surface end-to-end:
+4
;;; 1. Render scene into a 960×540 offscreen target (a few bright
+5
;;; moving circles + a dim backdrop).
+6
;;; 2. Composite the target onto the swap chain through a one-pass
+7
;;; bloom shader. Bright pixels (the circles) gain a visible glow
+8
;;; halo; dim pixels (the backdrop) pass through unmodified.
+9
;;;
+10
;;; Quits after ~5 seconds or when escape is pressed.
+11
+12
(define-library (test-postprocess-demo main)
+13
(import (sigil core)
+14
(sigil math)
+15
(sigil app)
+16
(sigil graphics))
+17
(export main)
+18
+19
(begin
+20
+21
(define WINDOW-W 960)
+22
(define WINDOW-H 540)
+23
+24
(define VERT-SOURCE
+25
(string-append
+26
"#version 410\n"
+27
"layout(location = 0) in vec4 coord;\n"
+28
"layout(location = 1) in vec4 color;\n"
+29
"out vec2 texUV;\n"
+30
"out vec4 iColor;\n"
+31
"void main() {\n"
+32
" gl_Position = vec4(coord.xy, 0.0, 1.0);\n"
+33
" texUV = coord.zw;\n"
+34
" iColor = color;\n"
+35
"}\n"))
+36
+37
;; One-pass bloom: 7x7 Gaussian-weighted blur of the
+38
;; threshold-extracted bright regions, added back on top of the
+39
;; original scene. Threshold uses smoothstep so it doesn't
+40
;; alias against pixel-aligned bright shapes; intensity
+41
;; multiplies the bloom contribution so bright pixels get a
+42
;; visible halo. Not production-quality (a real pipeline would
+43
;; downsample to a half-res target first) but enough to prove
+44
;; the surface works end to end.
+45
(define BLOOM-FRAG
+46
(string-append
+47
"#version 410\n"
+48
"uniform sampler2D iTexChannel0_iSmpChannel0;\n"
+49
"uniform float u_time;\n"
+50
"uniform vec2 u_resolution;\n"
+51
"uniform float u_intensity;\n"
+52
"uniform float u_threshold;\n"
+53
"uniform float u_radius;\n"
+54
"in vec2 texUV;\n"
+55
"in vec4 iColor;\n"
+56
"out vec4 fragColor;\n"
+57
"\n"
+58
"vec3 sample_bright(vec2 uv) {\n"
+59
" vec3 c = texture(iTexChannel0_iSmpChannel0, uv).rgb;\n"
+60
" float luma = max(max(c.r, c.g), c.b);\n"
+61
" return c * smoothstep(u_threshold, u_threshold + 0.15, luma);\n"
+62
"}\n"
+63
"\n"
+64
"void main() {\n"
+65
" vec3 base = texture(iTexChannel0_iSmpChannel0, texUV).rgb;\n"
+66
" vec2 px_step = u_radius / u_resolution;\n"
+67
" vec3 bloom = vec3(0.0);\n"
+68
" float wsum = 0.0;\n"
+69
" // 7x7 Gaussian-ish kernel — sigma ~ 1.5 cells.\n"
+70
" for (int j = -3; j <= 3; j++) {\n"
+71
" for (int i = -3; i <= 3; i++) {\n"
+72
" float w = exp(-float(i*i + j*j) * 0.22);\n"
+73
" bloom += sample_bright(texUV + vec2(i, j) * px_step) * w;\n"
+74
" wsum += w;\n"
+75
" }\n"
+76
" }\n"
+77
" bloom /= wsum;\n"
+78
" // Slight time-driven pulse so the auto-uniform u_time is\n"
+79
" // clearly exercised on capture.\n"
+80
" float pulse = 0.85 + 0.15 * sin(u_time * 1.7);\n"
+81
" vec3 outc = base + bloom * (u_intensity * pulse);\n"
+82
" fragColor = vec4(outc, 1.0) * iColor;\n"
+83
"}\n"))
+84
+85
;; Three orbiting circles in saturated colors — saturation is what
+86
;; makes the bloom halo unambiguous. The backdrop stays dim so it
+87
;; doesn't compete with the bloom.
+88
(define (draw-bright-scene t)
+89
(set-color 0.04 0.05 0.10 1.0)
+90
(draw-filled-rect 0.0 0.0 (inexact WINDOW-W) (inexact WINDOW-H))
+91
(let* ((cx (* 0.5 (inexact WINDOW-W)))
+92
(cy (* 0.5 (inexact WINDOW-H)))
+93
(r 140.0))
+94
;; Three orbiting circles, 120° apart, drawn additively so
+95
;; overlap regions get extra bright (= more bloom).
+96
(with-additive-blend
+97
(set-color 1.0 0.35 0.35 1.0)
+98
(draw-filled-circle (+ cx (* r (cos t)))
+99
(+ cy (* r (sin t))) 28.0 32)
+100
(set-color 0.35 1.0 0.45 1.0)
+101
(let ((a (+ t (/ (* 2.0 3.14159265) 3.0))))
+102
(draw-filled-circle (+ cx (* r (cos a)))
+103
(+ cy (* r (sin a))) 28.0 32))
+104
(set-color 0.40 0.55 1.0 1.0)
+105
(let ((a (+ t (/ (* 4.0 3.14159265) 3.0))))
+106
(draw-filled-circle (+ cx (* r (cos a)))
+107
(+ cy (* r (sin a))) 28.0 32))
+108
;; A small, very bright white core in the middle pulsing on
+109
;; t — gives the bloom a steady focal point.
+110
(set-color 1.0 1.0 1.0 (+ 0.6 (* 0.3 (sin (* t 2.5)))))
+111
(draw-filled-circle cx cy 16.0 32))))
+112
+113
(define (game-loop)
+114
(gfx-setup)
+115
(set-viewport WINDOW-W WINDOW-H)
+116
(let* ((rt (make-render-target WINDOW-W WINDOW-H))
+117
(bloom (load-shader VERT-SOURCE BLOOM-FRAG)))
+118
(set-shader-uniform bloom 'u_intensity 1.4)
+119
(set-shader-uniform bloom 'u_threshold 0.55)
+120
(set-shader-uniform bloom 'u_radius 3.5)
+121
(let loop ((elapsed 0.0))
+122
(let ((dt (wait-frame)))
+123
(with-render-target rt
+124
(draw-bright-scene elapsed))
+125
(with-frame
+126
(clear-screen 0.0 0.0 0.0 1.0)
+127
(with-shader bloom
+128
(draw-render-target rt 0.0 0.0
+129
(inexact WINDOW-W)
+130
(inexact WINDOW-H))))
+131
(cond
+132
((key-pressed? 'escape) (request-quit))
+133
((quit-requested?) #t)
+134
((> elapsed 5.0) (request-quit))
+135
(else (loop (+ elapsed dt))))))
+136
(shader-free! bloom)
+137
(render-target-free! rt))
+138
(gfx-shutdown))
+139
+140
(define (main . _args)
+141
(run-game "sigil-graphics bloom postprocess demo"
+142
WINDOW-W WINDOW-H game-loop)
+143
0)))