From 345c1b592ec3f662c7625b5381acb76ee10bf5e9 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sat, 31 Jan 2026 21:07:04 +1000 Subject: [PATCH 01/11] remove backups --- www/index.html.bak | 38 -------------------------------------- www/shader.html | 19 ------------------- 2 files changed, 57 deletions(-) delete mode 100644 www/index.html.bak delete mode 100644 www/shader.html diff --git a/www/index.html.bak b/www/index.html.bak deleted file mode 100644 index a8d3f1c..0000000 --- a/www/index.html.bak +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - -
-
- -
- grub> boot -
- -
- ERROR: Root device mounted successfully, but /sbin/init does not exist.

- - Bailing out, you are on your own.
- Good luck

- - sh: can't access tty; job control turned off

-
- - -
- [rootfs ]# - do butterflies cry when they're sad? -
- -
- Segmentation fault (core dumped) -
- -
-
- - diff --git a/www/shader.html b/www/shader.html deleted file mode 100644 index fd65714..0000000 --- a/www/shader.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - WebGL Demo - - - - - - - - - - - - - From b03962ec0a98a8de007af348fae7b6f22c60bd71 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 1 Feb 2026 15:42:07 +1000 Subject: [PATCH 02/11] organise repo --- .gitignore | 1 + palette.theme => docs/palette.theme | 0 bakeimgs => scripts/bakeimgs | 0 serve => scripts/serve | 0 www/css/typing-merge.css | 110 ---------------------------- www/css/typing.css | 11 ++- www/index.html | 2 +- 7 files changed, 7 insertions(+), 117 deletions(-) rename palette.theme => docs/palette.theme (100%) rename bakeimgs => scripts/bakeimgs (100%) rename serve => scripts/serve (100%) delete mode 100644 www/css/typing-merge.css diff --git a/.gitignore b/.gitignore index 30c9307..c341ed4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ bake/ .sass-cache/ +sandbox/ diff --git a/palette.theme b/docs/palette.theme similarity index 100% rename from palette.theme rename to docs/palette.theme diff --git a/bakeimgs b/scripts/bakeimgs similarity index 100% rename from bakeimgs rename to scripts/bakeimgs diff --git a/serve b/scripts/serve similarity index 100% rename from serve rename to scripts/serve diff --git a/www/css/typing-merge.css b/www/css/typing-merge.css deleted file mode 100644 index 4e54f42..0000000 --- a/www/css/typing-merge.css +++ /dev/null @@ -1,110 +0,0 @@ -.centered { - position: absolute; - inset: 0 0 0 0; - margin: auto; - - display: flex; -} - -.heading { - font-family: monospace; - font-size: 2em; - font-weight: bold; - color: #ffc0cb; /* #ac4aed */ -} - -/* =========================================================== * - * Type Writer Effect * -/* =========================================================== */ - -#typing-wrapper { - margin: auto auto; - width: 71ch; /* prompt + command + cursor length */ - height: 21ch; - text-align: start; - - border: 0.5ch solid #ffc0cb; /* #ac4aed */ - background-color: #0e0d14; - padding: 20px; - - display: flex; - flex-direction: column; - justify-content: start; - align-content: center; - align-items: start; -} - -#typing-prompt { - width: 10ch; /* prompt + command length */ - animation: kfs-typing 0.5s steps(4), kfs-cursor-blink 1.2s steps(1, start) 0.6s forwards; - white-space: nowrap; - overflow: hidden; - border-right: 1ch solid; - margin-bottom: 0.5ch; -} - -#typing-result { - /* "4.8s" means the result is shown 1.8s after typing ends */ - animation: unhide 1s 1.8s forwards; - visibility: hidden; - white-space: nowrap; /* preserve linebreaks */ -} - -#typing-prompt-segfault { - width: 47ch; /* prompt + command length */ - /* animation: kfs-typing-segfault 3s steps(36) 2.6s, cursor-blink 0.6s steps(1, start) 3s infinite alternate; */ - animation: kfs-typing-segfault 3s steps(36) 4s forwards, cursor-blink-segfault 0.6s steps(1, start) 7.1s infinite alternate; - white-space: nowrap; - overflow: hidden; - border-right: 1ch solid; - margin-bottom: 0.5ch; - visibility: hidden; -} - -#typing-result-segfault { - /* "4.8s" means the result is shown 1.8s after typing ends */ - animation: unhide 1s 8.3s forwards; - visibility: hidden; - white-space: nowrap; /* preserve linebreaks */ -} - -@keyframes kfs-typing { - from { - width: 6ch; /* ignore prompt width */ - } -} - -@keyframes kfs-typing-segfault { - from { - width: 11ch; /* ignore prompt width */ - visibility: visible; - } - to { - visibility: visible; - } -} - -@keyframes kfs-cursor-blink { - from { - border-color: transparent; - } - 50% { - border-color: currentColor; - } - to { - border-color: transparent; - } -} - -@keyframes cursor-blink-segfault { - 50% { - border-color: transparent; - } -} - -@keyframes unhide { - to { - visibility: visible; - } -} - diff --git a/www/css/typing.css b/www/css/typing.css index 723506b..34226b2 100644 --- a/www/css/typing.css +++ b/www/css/typing.css @@ -1,9 +1,3 @@ -html, body { - height: 100%; - margin: 0; - background-color: #0e0d14; -} - .centered { position: absolute; inset: 0 0 0 0; @@ -30,6 +24,7 @@ html, body { text-align: start; border: 0.5ch solid #ffc0cb; /* #ac4aed */ + background-color: #0e0d14; padding: 20px; display: flex; @@ -84,6 +79,9 @@ html, body { width: 11ch; /* ignore prompt width */ visibility: visible; } + 25% { + width: 11ch; + } to { visibility: visible; } @@ -112,3 +110,4 @@ html, body { visibility: visible; } } + diff --git a/www/index.html b/www/index.html index 700e299..7af52bb 100644 --- a/www/index.html +++ b/www/index.html @@ -11,7 +11,7 @@ - + From ffad884915df9ed6f50f02094e14d0aafa226022 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 1 Feb 2026 15:42:28 +1000 Subject: [PATCH 03/11] brownian motion experiments --- www/shaders/fbm.glsl | 97 ++++++++++ www/shaders/optimised.glsl | 323 ++++++++++++++++++++++++++++++++ www/shaders/segfault.glsl | 4 +- www/shaders/working.glsl | 368 +++++++++++++++++++++++++++++++++++++ 4 files changed, 790 insertions(+), 2 deletions(-) create mode 100644 www/shaders/fbm.glsl create mode 100644 www/shaders/optimised.glsl create mode 100644 www/shaders/working.glsl diff --git a/www/shaders/fbm.glsl b/www/shaders/fbm.glsl new file mode 100644 index 0000000..b35ba8f --- /dev/null +++ b/www/shaders/fbm.glsl @@ -0,0 +1,97 @@ +// Author @patriciogv - 2015 +// http://patriciogonzalezvivo.com + +#ifdef GL_ES +precision mediump float; +#endif + +uniform vec2 u_resolution; +uniform vec2 u_mouse; +uniform float u_time; + +float random (in vec2 _st) { + return fract(sin(dot(_st.xy, + vec2(12.9898,78.233)))* + 43758.5453123); +} + +// Based on Morgan McGuire @morgan3d +// https://www.shadertoy.com/view/4dS3Wd +float noise (in vec2 _st) { + vec2 i = floor(_st); + vec2 f = fract(_st); + + // Four corners in 2D of a tile + float a = random(i); + float b = random(i + vec2(1.0, 0.0)); + float c = random(i + vec2(0.0, 1.0)); + float d = random(i + vec2(1.0, 1.0)); + + vec2 u = f * f * (3.0 - 2.0 * f); + + return mix(a, b, u.x) + + (c - a)* u.y * (1.0 - u.x) + + (d - b) * u.x * u.y; +} + +#define NUM_OCTAVES 100 +// Brightness (0.0 - 1.0) +#define BRIGHTNESS 0.55 +#define LACUNARITY 2.0 +#define GAIN 0.5 +#define SHIFT 100.0 + +// VAR_THETA defines whether to compute +// sin/cos values at runtime or +#define VAR_THETA 0 +#define THETA 0.5 +#if (VAR_THETA == 1) +#endif + +float fbm ( in vec2 _st) { + float v = 0.0; + float a = BRIGHTNESS; + // Rotate to reduce axial bias + mat2 T = LACUNARITY * mat2(cos(THETA), sin(THETA), + -sin(THETA), cos(THETA)); + for (int i = 0; i < NUM_OCTAVES; ++i) { + _st = T * _st + SHIFT; + v += noise(_st) * a; + a *= GAIN; + } + return v; +} + +#define SCALE 3. + +void main() { + vec2 st = gl_FragCoord.xy/u_resolution.xy*SCALE; + // st += st * abs(sin(u_time*0.1)*3.0); + + vec2 q = vec2( + fbm( st + 0.*u_time), + fbm( st + vec2(1.0)) + ); + + vec2 r = vec2( + fbm( st + 1.0*q + vec2(1.7,9.2)+ 0.15*u_time ), + fbm( st + 1.0*q + vec2(8.3,2.8)+ 0.126*u_time) + ); + + float f = fbm(st+r); + + vec3 color = mix(vec3(0.101961,0.619608,0.666667), + vec3(0.666667,0.666667,0.498039), + clamp((f*f)*4.0,0.0,1.0)); + + color = mix(color, + vec3(0,0,0.164706), + clamp(length(q),0.0,1.0)); + + color = mix(color, + vec3(0.666667,1,1), + clamp(length(r.x),0.0,1.0)); + + gl_FragColor = vec4((f*f*f+.6*f*f+.5*f)*color,1.); +} + diff --git a/www/shaders/optimised.glsl b/www/shaders/optimised.glsl new file mode 100644 index 0000000..9d08431 --- /dev/null +++ b/www/shaders/optimised.glsl @@ -0,0 +1,323 @@ +/* BTW: You can use ANY function as the noise/plasma function + * It just needs to return a float in the range 0.0 - 17.0 + * But shouldn't return 0.0 or 17.0 (they're exclusive bounds). + * View this shader on shadertoy: https://www.shadertoy.com/view/t3tSRj# + */ + +// is highp wasteful for this shader? +#ifdef GL_ES +# ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +# else +precision mediump float; +# endif +#endif + +uniform float u_time; +uniform vec2 u_resolution; + +/* ==== Text Colouring ==== */ +#define PHOSPHOR_COL vec4(196./255., 167./255., 231./255., 1.) +// #define BG_COL vec4(0.2, 0.0, 0.2, 0.5) +#define BG_COL vec4(14./255., 13./255., 20./255., 1.) +/* ======= Text Size ======= */ +#define FONT_SIZE vec2(10.,20.) +#define ROWCOLS vec2(80., 24.) + +// =================================================================== +// Library Functions +// +float rand (in vec2 _st) { + return fract(sin(dot(_st.xy, vec2(12.9898,78.233))) * 43758.5453123); +} + +float quantise(float val, float n) { + return clamp(floor(val * n), 0.0, n) / n; +} + +float roundSquare(vec2 p, vec2 b, float r) { + return length(max(abs(p)-b,0.0))-r; +} + + +// =================================================================== +// VT220 Font Rendering +// Author/Source : https://www.shadertoy.com/view/llSXDV +// +#define l(y,a,b) roundLine(p, vec2(float(a), float(y)), vec2(float(b), float(y))) +float roundLine(vec2 p, vec2 a, vec2 b) { + b -= a + vec2(1.0,0.); + p -= a; + float f = length(p-clamp(dot(p,b)/dot(b,b),0.0,1.0)*b); + if (u_resolution.y < 320.) // attempt to get rid of aliasing on small resolution + return smoothstep(1.0, 0.9, f); + else if (u_resolution.y < 720.) + return smoothstep(0.75, 0.5, f); + else + return smoothstep(1., 0., f); +} + +float vt220Font(vec2 p, float c) { + if (c < 1.) return 0.; + if (p.y > 16.) { + if (c > 2.) return 0.0; + if (c > 1.) return l(17,1,9); + } + if (p.y > 14.) { + if (c > 16.) return l(15,3,8); + if (c > 15.) return l(15,1,8); + if (c > 14.) return l(15,1,3)+ l(15,7,9); + if (c > 13.) return l(15,2,8); + if (c > 12.) return l(15,1,9); + if (c > 11.) return l(15,2,8); + if (c > 10.) return l(15,1,3)+ l(15,6,8); + if (c > 9.) return l(15,4,6); + if (c > 8.) return l(15,2,4)+ l(15,5,7); + if (c > 7.) return l(15,2,8); + if (c > 6.) return l(15,2,8); + if (c > 5.) return l(15,2,8); + if (c > 4.) return l(15,2,9); + if (c > 3.) return l(15,1,8); + if (c > 2.) return l(15,2,9); + } + if (p.y > 12.) { + if (c > 16.) return l(13,2,4)+ l(13,7,9); + if (c > 15.) return l(13,2,4)+ l(13,7,9); + if (c > 14.) return l(13,1,3)+ l(13,7,9); + if (c > 13.) return l(13,1,3)+ l(13,7,9); + if (c > 12.) return l(13,1,3); + if (c > 11.) return l(13,4,6); + if (c > 10.) return l(13,2,4)+ l(13,5,9); + if (c > 9.) return l(13,2,8); + if (c > 8.) return l(13,2,4)+ l(13,5,7); + if (c > 7.) return l(13,1,3)+ l(13,7,9); + if (c > 6.) return l(13,1,3)+ l(13,7,9); + if (c > 5.) return l(13,1,3)+ l(13,7,9); + if (c > 4.) return l(13,1,3)+ l(15,2,9); + if (c > 3.) return l(13,1,4)+ l(13,7,9); + if (c > 2.) return l(13,1,3)+ l(13,6,9); + } + if (p.y > 10.) { + if (c > 16.) return l(11,1,3); + if (c > 15.) return l(11,2,4)+ l(11,7,9); + if (c > 14.) return l(11,1,9); + if (c > 13.) return l(11,7,9); + if (c > 12.) return l(11,2,5); + if (c > 11.) return l(11,4,6); + if (c > 10.) return l(11,3,5)+ l(11,6,8); + if (c > 9.) return l(11,4,6)+ l(11,7,9); + if (c > 8.) return l(11,1,8); + if (c > 7.) return l(11,1,3)+ l(11,7,9); + if (c > 6.) return l(11,1,3)+ l(11,7,9); + if (c > 5.) return l(11,1,3)+ l(11,7,9); + if (c > 4.) return l(11,1,3); + if (c > 3.) return l(11,1,3)+ l(11,7,9); + if (c > 2.) return l(11,2,9); + } + if (p.y > 8.) { + if (c > 16.) return l(9,1,3); + if (c > 15.) return l(9,2,8); + if (c > 14.) return l(9,1,3)+ l(9,7,9); + if (c > 13.) return l(9,4,8); + if (c > 12.) return l(9,4,8); + if (c > 11.) return l(9,4,6); + if (c > 10.) return l(9,4,6); + if (c > 9.) return l(9,2,8); + if (c > 8.) return l(9,2,4)+ l(9,5,7); + if (c > 7.) return l(9,1,3)+ l(9,7,9); + if (c > 6.) return l(9,1,3)+ l(9,7,9); + if (c > 5.) return l(9,1,3)+ l(9,7,9); + if (c > 4.) return l(9,1,3)+ l(9,7,9); + if (c > 3.) return l(9,1,4)+ l(9,7,9); + if (c > 2.) return l(9,7,9); + } + if (p.y > 6.) { + if (c > 16.) return l(7,1,3); + if (c > 15.) return l(7,2,4)+ l(7,7,9); + if (c > 14.) return l(7,2,4)+ l(7,6,8); + if (c > 13.) return l(7,5,7); + if (c > 12.) return l(7,7,9); + if (c > 11.) return l(7,2,6); + if (c > 10.) return l(7,2,4)+ l(7,5,7); + if (c > 9.) return l(7,1,3)+ l(7,4,6); + if (c > 8.) return l(7,1,8); + if (c > 7.) return l(7,2,8); + if (c > 6.) return l(7,2,8); + if (c > 5.) return l(7,2,8); + if (c > 4.) return l(7,2,8); + if (c > 3.) return l(7,1,8); + if (c > 2.) return l(7,2,8); + } + if (p.y > 4.) { + if (c > 16.) return l(5,2,4)+ l(5,7,9); + if (c > 15.) return l(5,2,4)+ l(5,7,9); + if (c > 14.) return l(5,3,7); + if (c > 13.) return l(5,6,8); + if (c > 12.) return l(5,1,3)+ l(5,7,9); + if (c > 11.) return l(5,3,6); + if (c > 10.) return l(5,1,5)+ l(5,6,8); + if (c > 9.) return l(5,2,8); + if (c > 8.) return l(5,2,4)+ l(5,5,7); + if (c > 7.) return 0.; + if (c > 6.) return 0.; + if (c > 5.) return 0.; + if (c > 4.) return 0.; + if (c > 3.) return l(5,1,3); + if (c > 2.) return 0.; + } + if (p.y > 2.) { + if (c > 16.) return l(3,3,8); + if (c > 15.) return l(3,1,8); + if (c > 14.) return l(3,4,6); + if (c > 13.) return l(3,1,9); + if (c > 12.) return l(3,2,8); + if (c > 11.) return l(3,4,6); + if (c > 10.) return l(3,2,4)+ l(3,7,9); + if (c > 9.) return l(3,4,6); + if (c > 8.) return l(3,2,4)+ l(3,5,7); + if (c > 7.) return l(3,2,4)+ l(3,6,8); + if (c > 6.) return l(3,1,3)+ l(3,4,7); + if (c > 5.) return l(3,2,4)+ l(3,6,8); + if (c > 4.) return 0.; + if (c > 3.) return l(3,1,3); + if (c > 2.) return 0.; + } + else { + if (c > 7.) return 0.; + if (c > 6.) return l(1,2,5)+ l(1,6,8); + } + return 0.0; +} + + +// =================================================================== +// Text Effects +// textLines(...) is for simulating the writing of random characters in line formats +// https://www.shadertoy.com/view/llSXDV (same author as VT220 font rendering) +// +float textLines(vec2 uvG) { + float wt = 5. * (u_time + 0.5*sin(u_time*1.4) + 0.2*sin(u_time*2.9)); // wobbly time + vec2 uvGt = uvG + vec2(0., floor(wt)); + float ll = rand(vec2(uvGt.y, - 1.)) * ROWCOLS.x; // line length + + if (uvG.y > ROWCOLS.y - 2.){ + if (ceil(uvG.x) == floor(min(ll, fract(wt)*ROWCOLS.x))) + return 2.; + if (ceil(uvG.x) > floor(min(ll, fract(wt)*ROWCOLS.x))) + return 0.; + } + if (uvGt.x > 5. && rand(uvGt) < .075) + return 0.; + if (max(5., uvGt.x) > ll) + return 0.; + + return rand(uvGt)*15. + 2.; +} + + +// =================================================================== +// Noise Generation Algorithms +// noise(...) is for the main noise generation algorithm (very tunable) +// https://www.shadertoy.com/view/MsdGWn +// + +// Based on Morgan McGuire @morgan3d +// https://www.shadertoy.com/view/4dS3Wd +float noise (in vec2 _st) { + vec2 i = floor(_st); + vec2 f = fract(_st); + + // Four corners in 2D of a tile + float a = rand(i); + float b = rand(i + vec2(1.0, 0.0)); + float c = rand(i + vec2(0.0, 1.0)); + float d = rand(i + vec2(1.0, 1.0)); + + vec2 u = f * f * (3.0 - 2.0 * f); + + return mix(a, b, u.x) + + (c - a)* u.y * (1.0 - u.x) + + (d - b) * u.x * u.y; +} + +#define NUM_OCTAVES 100 +// Brightness (0.0 - 1.0) +#define BRIGHTNESS 0.55 +#define LACUNARITY 2.0 +#define GAIN 0.5 +#define SHIFT 100.0 + +// VAR_THETA defines whether to compute +// sin/cos values at runtime or +#define VAR_THETA 0 +#define THETA 0.5 +#if (VAR_THETA == 1) +#endif + +float fbm( in vec2 _st) { + float v = 0.0; + float a = BRIGHTNESS; + // Rotate to reduce axial bias + mat2 T = LACUNARITY * mat2(cos(THETA), sin(THETA), + -sin(THETA), cos(THETA)); + for (int i = 0; i < NUM_OCTAVES; ++i) { + _st = T * _st + SHIFT; + v += noise(_st) * a; + a *= GAIN; + } + return v; +} + + +#define SCALE_UV 3. +#define SCALE_TIME 1. + +// XXX: TODO: use two shaders, the first on a MUCH lower resolution (one pixel for each text character) +// XXX: TODO: then the second should map those pixel values to higher resolution text characters +void main() { + vec2 uv = gl_FragCoord.xy / u_resolution; + vec2 st = uv * SCALE; + + // // uvNoise = ceil(uvNoise * ROWCOLS) / ROWCOLS; + // float val = noise(st); + // // Noise is fed through a sigmoid function, then quantised to integer range 0-17 + // val = (exp(val) / 2.71828); // increase contrast (normalised 0.0 - 1.0) + // val = 1.0 / val; + // val *= 1.0 / (1.0 + exp(-val)) - 0.5; // subtraction value is tunable (range 0.0 - 0.5) + // val *= quantise(val, 17.0); // quantise by 17 then normalise back to 0.0 - 1.0 + // val = pow(18.0, val) - 1.0; // TODO: try changing 18.0 to 200.0 and you'll notice some pretty changes :) + + // // VT220 Font Rendering + // vec2 base = uv; + // base.y = 1 - base.y; + // vec2 uvT = ROWCOLS * FONT_SIZE * base; + // vec2 uvG = floor(ROWCOLS * base; + // gl_FragColor = vt220Font(uvT - uvG * FONT_SIZE, val) * PHOSPHOR_COL + BG_COL; + + vec2 q = vec2( + fbm( st + 0.*u_time), + fbm( st + vec2(1.0)) + ); + + vec2 r = vec2( + fbm( st + 1.0*q + vec2(1.7,9.2)+ 0.15*u_time ), + fbm( st + 1.0*q + vec2(8.3,2.8)+ 0.126*u_time) + ); + + float f = fbm(st+r); + + vec3 color = mix(vec3(0.101961,0.619608,0.666667), + vec3(0.666667,0.666667,0.498039), + clamp((f*f)*4.0,0.0,1.0)); + + color = mix(color, + vec3(0,0,0.164706), + clamp(length(q),0.0,1.0)); + + color = mix(color, + vec3(0.666667,1,1), + clamp(length(r.x),0.0,1.0)); + + gl_FragColor = vec4((f*f*f+.6*f*f+.5*f)*color,1.); +} + diff --git a/www/shaders/segfault.glsl b/www/shaders/segfault.glsl index a3d73a8..adabb14 100644 --- a/www/shaders/segfault.glsl +++ b/www/shaders/segfault.glsl @@ -15,9 +15,9 @@ uniform float u_time; uniform vec2 u_resolution; /* ==== Text Colouring ==== */ -#define PHOSPHOR_COL vec4(1, 1., 1., 1.) +#define PHOSPHOR_COL vec4(196./255., 167./255., 231./255., 1.) // #define BG_COL vec4(0.2, 0.0, 0.2, 0.5) -#define BG_COL vec4(0.0, 0.0, 0.0, 1.) +#define BG_COL vec4(14./255., 13./255., 20./255., 1.) /* ======= Text Size ======= */ #define FONT_SIZE vec2(10.,20.) #define ROWCOLS vec2(80., 24.) diff --git a/www/shaders/working.glsl b/www/shaders/working.glsl new file mode 100644 index 0000000..71a0872 --- /dev/null +++ b/www/shaders/working.glsl @@ -0,0 +1,368 @@ +// WARNING: NOTE: this works on https://glslsandbox.com/e +precision highp float; + +uniform float time; +uniform vec2 resolution; + +/* ==== Text Colouring ==== */ +#define PHOSPHOR_COL vec4(1, 1., 1., 1.) +#define BG_COL vec4(0.2, 0.0, 0.2, 0.) +/* ======= Text Size ======= */ +#define FONT_SIZE vec2(10.,20.) +#define ROWCOLS vec2(80., 24.) +/* === Text Bloom Effect === */ +#define WIDTH 1.2 +#define HEIGHT 0.7 +#define SMOOTH 0.004 +/* ====== Smoke Noise ====== */ +const int noiseSwirlSteps = 0; +const float noiseSwirlValue = 1.; +const float noiseSwirlStepValue = noiseSwirlValue / float(noiseSwirlSteps); +const float noiseScale = 1.0; +const float noiseTimeScale = 0.1; + +// =================================================================== +// Library Functions +// +float rand(vec2 co) { + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +float quantise(float val, float n) { + return clamp(floor(val * n), 0.0, n) / n; +} + +float roundSquare(vec2 p, vec2 b, float r) { + return length(max(abs(p)-b,0.0))-r; +} + +// standard roundSquare +float stdRS(vec2 uv, float r) { + return roundSquare(uv - 0.5, vec2(WIDTH, HEIGHT) + r, 0.05); +} + +// =================================================================== +// Description : Array and textureless GLSL 2D/3D/4D simplex +// noise functions. +// Author : Ian McEwan, Ashima Arts. +// Maintainer : ijm +// Lastmod : 20110822 (ijm) +// License : Copyright (C) 2011 Ashima Arts. All rights reserved. +// Distributed under the MIT License. See LICENSE file. +// https://github.com/ashima/webgl-noise +// +vec3 mod289(vec3 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +vec4 mod289(vec4 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +vec4 permute(vec4 x) { + return mod289(((x*34.0)+1.0)*x); +} + +vec4 taylorInvSqrt(vec4 r) { + return 1.79284291400159 - 0.85373472095314 * r; +} + +float simplex(vec3 v) { + const vec2 C = vec2(1.0/6.0, 1.0/3.0) ; + const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); + + // First corner + vec3 i = floor(v + dot(v, C.yyy) ); + vec3 x0 = v - i + dot(i, C.xxx) ; + + // Other corners + vec3 g = step(x0.yzx, x0.xyz); + vec3 l = 1.0 - g; + vec3 i1 = min( g.xyz, l.zxy ); + vec3 i2 = max( g.xyz, l.zxy ); + + vec3 x1 = x0 - i1 + C.xxx; + vec3 x2 = x0 - i2 + C.yyy; + vec3 x3 = x0 - D.yyy; + + // Permutations + i = mod289(i); + vec4 p = permute( permute( permute( + i.z + vec4(0.0, i1.z, i2.z, 1.0 )) + + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) + + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); + + // Gradients: 7x7 points over a square, mapped onto an octahedron. + // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) + float n_ = 0.142857142857; // 1.0/7.0 + vec3 ns = n_ * D.wyz - D.xzx; + + vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7) + + vec4 x_ = floor(j * ns.z); + vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N) + + vec4 x = x_ *ns.x + ns.yyyy; + vec4 y = y_ *ns.x + ns.yyyy; + vec4 h = 1.0 - abs(x) - abs(y); + + vec4 b0 = vec4( x.xy, y.xy ); + vec4 b1 = vec4( x.zw, y.zw ); + + vec4 s0 = floor(b0)*2.0 + 1.0; + vec4 s1 = floor(b1)*2.0 + 1.0; + vec4 sh = -step(h, vec4(0.0)); + + vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ; + vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ; + + vec3 p0 = vec3(a0.xy,h.x); + vec3 p1 = vec3(a0.zw,h.y); + vec3 p2 = vec3(a1.xy,h.z); + vec3 p3 = vec3(a1.zw,h.w); + + //Normalise gradients + vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); + p0 *= norm.x; + p1 *= norm.y; + p2 *= norm.z; + p3 *= norm.w; + + // Mix final noise value + vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); + m = m * m; + return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3) ) ); +} + + +// =================================================================== +// VT220 Font Rendering +// Author/Source : https://www.shadertoy.com/view/llSXDV +// +#define l(y,a,b) roundLine(p, vec2(float(a), float(y)), vec2(float(b), float(y))) +float roundLine(vec2 p, vec2 a, vec2 b) { + b -= a + vec2(1.0,0.); + p -= a; + float f = length(p-clamp(dot(p,b)/dot(b,b),0.0,1.0)*b); + if (resolution.y < 320.) // attempt to get rid of aliasing on small resolution + return smoothstep(1.0, 0.9, f); + else if (resolution.y < 720.) + return smoothstep(0.75, 0.5, f); + else + return smoothstep(1., 0., f); +} + +float vt220Font(vec2 p, float c) { + if (c < 1.) return 0.; + if (p.y > 16.) { + if (c > 2.) return 0.0; + if (c > 1.) return l(17,1,9); + } + if (p.y > 14.) { + if (c > 16.) return l(15,3,8); + if (c > 15.) return l(15,1,8); + if (c > 14.) return l(15,1,3)+ l(15,7,9); + if (c > 13.) return l(15,2,8); + if (c > 12.) return l(15,1,9); + if (c > 11.) return l(15,2,8); + if (c > 10.) return l(15,1,3)+ l(15,6,8); + if (c > 9.) return l(15,4,6); + if (c > 8.) return l(15,2,4)+ l(15,5,7); + if (c > 7.) return l(15,2,8); + if (c > 6.) return l(15,2,8); + if (c > 5.) return l(15,2,8); + if (c > 4.) return l(15,2,9); + if (c > 3.) return l(15,1,8); + if (c > 2.) return l(15,2,9); + } + if (p.y > 12.) { + if (c > 16.) return l(13,2,4)+ l(13,7,9); + if (c > 15.) return l(13,2,4)+ l(13,7,9); + if (c > 14.) return l(13,1,3)+ l(13,7,9); + if (c > 13.) return l(13,1,3)+ l(13,7,9); + if (c > 12.) return l(13,1,3); + if (c > 11.) return l(13,4,6); + if (c > 10.) return l(13,2,4)+ l(13,5,9); + if (c > 9.) return l(13,2,8); + if (c > 8.) return l(13,2,4)+ l(13,5,7); + if (c > 7.) return l(13,1,3)+ l(13,7,9); + if (c > 6.) return l(13,1,3)+ l(13,7,9); + if (c > 5.) return l(13,1,3)+ l(13,7,9); + if (c > 4.) return l(13,1,3)+ l(15,2,9); + if (c > 3.) return l(13,1,4)+ l(13,7,9); + if (c > 2.) return l(13,1,3)+ l(13,6,9); + } + if (p.y > 10.) { + if (c > 16.) return l(11,1,3); + if (c > 15.) return l(11,2,4)+ l(11,7,9); + if (c > 14.) return l(11,1,9); + if (c > 13.) return l(11,7,9); + if (c > 12.) return l(11,2,5); + if (c > 11.) return l(11,4,6); + if (c > 10.) return l(11,3,5)+ l(11,6,8); + if (c > 9.) return l(11,4,6)+ l(11,7,9); + if (c > 8.) return l(11,1,8); + if (c > 7.) return l(11,1,3)+ l(11,7,9); + if (c > 6.) return l(11,1,3)+ l(11,7,9); + if (c > 5.) return l(11,1,3)+ l(11,7,9); + if (c > 4.) return l(11,1,3); + if (c > 3.) return l(11,1,3)+ l(11,7,9); + if (c > 2.) return l(11,2,9); + } + if (p.y > 8.) { + if (c > 16.) return l(9,1,3); + if (c > 15.) return l(9,2,8); + if (c > 14.) return l(9,1,3)+ l(9,7,9); + if (c > 13.) return l(9,4,8); + if (c > 12.) return l(9,4,8); + if (c > 11.) return l(9,4,6); + if (c > 10.) return l(9,4,6); + if (c > 9.) return l(9,2,8); + if (c > 8.) return l(9,2,4)+ l(9,5,7); + if (c > 7.) return l(9,1,3)+ l(9,7,9); + if (c > 6.) return l(9,1,3)+ l(9,7,9); + if (c > 5.) return l(9,1,3)+ l(9,7,9); + if (c > 4.) return l(9,1,3)+ l(9,7,9); + if (c > 3.) return l(9,1,4)+ l(9,7,9); + if (c > 2.) return l(9,7,9); + } + if (p.y > 6.) { + if (c > 16.) return l(7,1,3); + if (c > 15.) return l(7,2,4)+ l(7,7,9); + if (c > 14.) return l(7,2,4)+ l(7,6,8); + if (c > 13.) return l(7,5,7); + if (c > 12.) return l(7,7,9); + if (c > 11.) return l(7,2,6); + if (c > 10.) return l(7,2,4)+ l(7,5,7); + if (c > 9.) return l(7,1,3)+ l(7,4,6); + if (c > 8.) return l(7,1,8); + if (c > 7.) return l(7,2,8); + if (c > 6.) return l(7,2,8); + if (c > 5.) return l(7,2,8); + if (c > 4.) return l(7,2,8); + if (c > 3.) return l(7,1,8); + if (c > 2.) return l(7,2,8); + } + if (p.y > 4.) { + if (c > 16.) return l(5,2,4)+ l(5,7,9); + if (c > 15.) return l(5,2,4)+ l(5,7,9); + if (c > 14.) return l(5,3,7); + if (c > 13.) return l(5,6,8); + if (c > 12.) return l(5,1,3)+ l(5,7,9); + if (c > 11.) return l(5,3,6); + if (c > 10.) return l(5,1,5)+ l(5,6,8); + if (c > 9.) return l(5,2,8); + if (c > 8.) return l(5,2,4)+ l(5,5,7); + if (c > 7.) return 0.; + if (c > 6.) return 0.; + if (c > 5.) return 0.; + if (c > 4.) return 0.; + if (c > 3.) return l(5,1,3); + if (c > 2.) return 0.; + } + if (p.y > 2.) { + if (c > 16.) return l(3,3,8); + if (c > 15.) return l(3,1,8); + if (c > 14.) return l(3,4,6); + if (c > 13.) return l(3,1,9); + if (c > 12.) return l(3,2,8); + if (c > 11.) return l(3,4,6); + if (c > 10.) return l(3,2,4)+ l(3,7,9); + if (c > 9.) return l(3,4,6); + if (c > 8.) return l(3,2,4)+ l(3,5,7); + if (c > 7.) return l(3,2,4)+ l(3,6,8); + if (c > 6.) return l(3,1,3)+ l(3,4,7); + if (c > 5.) return l(3,2,4)+ l(3,6,8); + if (c > 4.) return 0.; + if (c > 3.) return l(3,1,3); + if (c > 2.) return 0.; + } + else { + if (c > 7.) return 0.; + if (c > 6.) return l(1,2,5)+ l(1,6,8); + } + return 0.0; +} + + +// =================================================================== +// Noise Generation Algorithms +// textLines(...) is for simulating the writing of random characters in line formats +// https://www.shadertoy.com/view/llSXDV (same author as VT220 font rendering) +// +// smokeNoise(...) is for the main noise generation algorithm (very tunable) +// https://www.shadertoy.com/view/MsdGWn +// +float textLines(vec2 uvG) { + float wt = 5. * (time + 0.5*sin(time*1.4) + 0.2*sin(time*2.9)); // wobbly time + vec2 uvGt = uvG + vec2(0., floor(wt)); + float ll = rand(vec2(uvGt.y, - 1.)) * ROWCOLS.x; // line length + + if (uvG.y > ROWCOLS.y - 2.){ + if (ceil(uvG.x) == floor(min(ll, fract(wt)*ROWCOLS.x))) + return 2.; + if (ceil(uvG.x) > floor(min(ll, fract(wt)*ROWCOLS.x))) + return 0.; + } + if (uvGt.x > 5. && rand(uvGt) < .075) + return 0.; + if (max(5., uvGt.x) > ll) + return 0.; + + return rand(uvGt)*15. + 2.; +} + +float fbm3(vec3 v) { + float result = simplex(v); + result += simplex(v * 2.) / 2.; + result += simplex(v * 4.) / 4.; + result /= (1. + 1./2. + 1./4.); + return result; +} + +float fbm5(vec3 v) { + float result = simplex(v); + result += simplex(v * 2.) / 2.; + result += simplex(v * 4.) / 4.; + result += simplex(v * 8.) / 8.; + result += simplex(v * 16.) / 16.; + result /= (1. + 1./2. + 1./4. + 1./8. + 1./16.); + return result; +} + +float smokeNoise(vec3 v) { + // make it curl + for (int i=0; i Date: Sun, 1 Feb 2026 15:42:50 +1000 Subject: [PATCH 04/11] fix fpsLimit unused --- www/js/webgl-demo.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/www/js/webgl-demo.js b/www/js/webgl-demo.js index 5e58854..c8c8217 100644 --- a/www/js/webgl-demo.js +++ b/www/js/webgl-demo.js @@ -67,11 +67,8 @@ function renderShader(gl, vsSource, fsSource) { // objects we'll be drawing. const buffers = initBuffers(gl); - // Draw the scene - // drawScene(gl, programInfo, buffers, 0); - const fpsLimit = 30; - const fpsDelta = 1000 / 30; + const fpsDelta = 1000 / fpsLimit; // let timePrev = 0; // requestAnimationFrame asks the browser to call render, // providing the time in milliseconds since the page loaded @@ -128,7 +125,7 @@ function main() { `; // Fetch fragment shader program - fetchShader("segfault.glsl") + fetchShader("fbm.glsl") .then(fsSource => { renderShader(gl, vsSource, fsSource); }); From 1d06470ccd2918214df01b8e06bccfeeb46de4d3 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Mon, 2 Feb 2026 01:12:53 +1000 Subject: [PATCH 05/11] created ShadeMyCanvas (smc) --- www/index.html | 2 +- www/js/main.js | 19 ++ www/js/smc/README.md | 4 + .../{draw-scene.js => smc/draw-scene.js.bak} | 0 www/js/smc/errors.js | 10 + .../init-buffers.js.bak} | 0 www/js/smc/lib.js | 4 + www/js/{webgl-demo.js => smc/lib.js.bak} | 25 +- www/js/smc/progbuilder.js | 127 +++++++ www/js/smc/smc.js | 311 ++++++++++++++++++ www/js/smc/smc.js.bak | 37 +++ www/js/smc/util.js | 24 ++ 12 files changed, 539 insertions(+), 24 deletions(-) create mode 100644 www/js/main.js create mode 100644 www/js/smc/README.md rename www/js/{draw-scene.js => smc/draw-scene.js.bak} (100%) create mode 100644 www/js/smc/errors.js rename www/js/{init-buffers.js => smc/init-buffers.js.bak} (100%) create mode 100644 www/js/smc/lib.js rename www/js/{webgl-demo.js => smc/lib.js.bak} (83%) create mode 100644 www/js/smc/progbuilder.js create mode 100644 www/js/smc/smc.js create mode 100644 www/js/smc/smc.js.bak create mode 100644 www/js/smc/util.js diff --git a/www/index.html b/www/index.html index 7af52bb..f8308c9 100644 --- a/www/index.html +++ b/www/index.html @@ -9,7 +9,7 @@ - + diff --git a/www/js/main.js b/www/js/main.js new file mode 100644 index 0000000..a2aa9b7 --- /dev/null +++ b/www/js/main.js @@ -0,0 +1,19 @@ +import { Smc } from "./smc/smc.js" + +main(); + +function main() { + const canvas = document.querySelector("#gl-canvas"); + canvas.setAttribute('width', window.innerWidth); + canvas.setAttribute('height', window.innerHeight); + + new Smc(canvas) + .setMaxFps(30) + .setProgram(builder => + builder + // .fetchVertexShader("../shaders/segfault.glsl") + // .fetchFragmentShader("../shaders/segfault.glsl")) + ) + .run(); +} + diff --git a/www/js/smc/README.md b/www/js/smc/README.md new file mode 100644 index 0000000..c013131 --- /dev/null +++ b/www/js/smc/README.md @@ -0,0 +1,4 @@ +# Shade My Canvas +An easy to use and purely declarative wrapper for WebGL written in Javascript. +The main idea is to remove all the boilerplate required to render shader +programs, so you can focus on writing GLSL and not debugging WebGL. diff --git a/www/js/draw-scene.js b/www/js/smc/draw-scene.js.bak similarity index 100% rename from www/js/draw-scene.js rename to www/js/smc/draw-scene.js.bak diff --git a/www/js/smc/errors.js b/www/js/smc/errors.js new file mode 100644 index 0000000..3900885 --- /dev/null +++ b/www/js/smc/errors.js @@ -0,0 +1,10 @@ +export { SmcErr }; + +const SmcErr = { + UNSUPPORTED: 0, // unused + SHADER_COMPILATION: 1, + PROGRAM_INIT: 2, + ATTRIBUTE_MISSING: 3, + UNIFORM_MISSING: 4, + FETCH_SHADER: 5, +} diff --git a/www/js/init-buffers.js b/www/js/smc/init-buffers.js.bak similarity index 100% rename from www/js/init-buffers.js rename to www/js/smc/init-buffers.js.bak diff --git a/www/js/smc/lib.js b/www/js/smc/lib.js new file mode 100644 index 0000000..cbff55e --- /dev/null +++ b/www/js/smc/lib.js @@ -0,0 +1,4 @@ +import { Smc } from "./smc.js"; +import { SmcErr } from "./errors.js"; + +export { Smc, SmcErr }; diff --git a/www/js/webgl-demo.js b/www/js/smc/lib.js.bak similarity index 83% rename from www/js/webgl-demo.js rename to www/js/smc/lib.js.bak index c8c8217..4f08661 100644 --- a/www/js/webgl-demo.js +++ b/www/js/smc/lib.js.bak @@ -1,7 +1,7 @@ import { initBuffers } from "./init-buffers.js"; import { drawScene } from "./draw-scene.js"; -main(); +export { run }; // Initialize a shader program, so WebGL knows how to draw our data function initShaderProgram(gl, vsSource, fsSource) { @@ -27,25 +27,6 @@ function initShaderProgram(gl, vsSource, fsSource) { return program; } -// Creates a shader of the given type, uploads the source and compiles -function loadShader(gl, type, source) { - const shader = gl.createShader(type); - - // Send the source to the shader object - gl.shaderSource(shader, source); - - // Compile the shader program - gl.compileShader(shader); - - // See if it compiled successfully - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { - gl.deleteShader(shader); - throw new Error(`An error occurred compiling the shaders: ${gl.getShaderInfoLog(shader)}`); - } - - return shader; -} - function renderShader(gl, vsSource, fsSource) { const shaderProgram = initShaderProgram(gl, vsSource, fsSource); @@ -101,9 +82,7 @@ function fetchShader(name) { } -function main() { - const canvas = document.querySelector("#gl-canvas"); - // Initialize the GL context +function run(canvas) { const gl = canvas.getContext("webgl"); // XXX: TODO: use `window.addEventListener('resize', ...);` diff --git a/www/js/smc/progbuilder.js b/www/js/smc/progbuilder.js new file mode 100644 index 0000000..da86049 --- /dev/null +++ b/www/js/smc/progbuilder.js @@ -0,0 +1,127 @@ +import { SmcErr } from './errors.js'; + +export { SmcProgramBuilder }; + +class SmcProgramBuilder { + #gl; + #program; + + #isBuilt = false; + #hasVertexShader = false; + #hasFragmentShader = false; + + #defaultVertexShader = ` + attribute vec4 aVertex; + + void main() { + gl_Position = aVertex; + } + `; + + // TODO: reset the sample fragment shader back to the rainbow + #sampleFragmentShader = ` + precision mediump float; + + // uniform float uTime; + // uniform vec2 uResolution; + + void main() { + // vec2 uv = gl_FragCoord.xy / uResolution; + // vec3 col = 0.5 + 0.5 * cos(uTime + uv.xyx + vec3(0, 2, 4)); + // gl_FragColor = vec4(col, 1.0); + // gl_FragColor = vec4(216., 43., 72., 255.) / 255.; + + + float maxfc = max(gl_FragCoord.x, gl_FragCoord.y); + gl_FragColor = vec4(gl_FragCoord.xy, maxfc, maxfc) / maxfc; + } + `; + + + constructor(gl, raiseError) { + this.#gl = gl; + this.#program = this.#gl.createProgram(); + this.raiseError = raiseError; + } + + addVertexShader(source) { + this.#gl.attachShader( + this.#program, + this.#newShader( + this.#gl.VERTEX_SHADER, + source + ) + ) + this.#hasVertexShader = true; + return this; + } + + addFragmentShader(source) { + this.#gl.attachShader( + this.#program, + this.#newShader( + this.#gl.FRAGMENT_SHADER, + source + ) + ) + this.#hasFragmentShader = true; + return this; + } + + fetchVertexShader(uri) { + this.#fetchShader(uri, (source) => this.addVertexShader(source)); + return this; + } + + fetchFragmentShader(uri) { + this.#fetchShader(uri, (source) => this.addFragmentShader(source)); + return this; + } + + build() { + // avoid user accidental calls to build() + if (!this.#isBuilt) { + if (!this.#hasVertexShader) + this.addVertexShader(this.#defaultVertexShader) + if (!this.#hasFragmentShader) + this.addFragmentShader(this.#sampleFragmentShader); + + this.#gl.linkProgram(this.#program); + this.#gl.useProgram(this.#program); + } + return this.#program; + } + + // Creates a shader of the given type, uploads the source and compiles + #newShader(type, source) { + const shader = this.#gl.createShader(type); + this.#gl.shaderSource(shader, source); + this.#gl.compileShader(shader); + + if (!this.#gl.getShaderParameter(shader, this.#gl.COMPILE_STATUS)) { + this.#gl.deleteShader(shader); + const infoLog = this.#gl.getShaderInfoLog(shader); + this.raiseError( + SmcErr.SHADER_COMPILATION, + new Error(`An error occurred while compiling the shader: ${infoLog}`) + ); + } + + return shader; + } + + #fetchShader(uri, delegate) { + return fetch(uri) + .then(res => { + if (res.ok) + delegate(res.text()); + else { + this.raiseError( + SmcErr.FETCH_SHADER, + `Failed to load shader source ${url}: ${res.status} ${res.json()}`); + } + }); + } + +} + diff --git a/www/js/smc/smc.js b/www/js/smc/smc.js new file mode 100644 index 0000000..2f77955 --- /dev/null +++ b/www/js/smc/smc.js @@ -0,0 +1,311 @@ +import { SmcErr } from "./errors.js"; +import { SmcProgramBuilder } from "./progbuilder.js"; +import { hexToRgba } from "./util.js"; + +export { Smc, UniformType }; + +const UniformType = { + Float1: 0, + Float2: 1, + Float3: 2, + Float4: 3, + Int1: 4, + Int2: 5, + Int3: 6, + Int4: 7, +}; + +class Smc { + #canvas; + #gl; + + // Position array of a "full-screen" quad (encoded as TRIANGLE_STRIP) + // Ref: https://en.wikipedia.org/wiki/Triangle_strip + // NOTE: +x,+y is top-right & -x,-y is bottom-left + #verticesFullscreen = [ + -1.0, 1.0, + -1.0, -1.0, + 1.0, 1.0, + 1.0, -1.0, + ]; + #vertices = this.#verticesFullscreen; + #attributes = new Map(); + #uniforms = new Map(); + #program = null; + #clearBitFlags; + + #maxFps; + #minDeltaTimeMs; // in milliseconds + #prevTimeMs = 0; + + #errorDelegate = (_, error) => { throw error }; + #initDelegate = (_) => { }; + #resizeDelegate = (_) => { }; + + constructor(canvas) { + this.raiseError = this.raiseError.bind(this); + this.render = this.render.bind(this); + this.renderLoop = this.renderLoop.bind(this); + + this.#canvas = canvas; + this.#gl = Smc.#getWebGlContext(canvas); + // NOTE: smc.isWebGlSupported() should be queried prior + if (this.#gl == null) + throw new Error("Unable to initialize WebGL. Your browser or machine may not support it."); + + // clear the entire depth buffer when this.#gl.clear is called + this.#gl.clearDepth(1.0); + this.#clearBitFlags = this.#gl.COLOR_BUFFER_BIT | this.#gl.DEPTH_BUFFER_BIT; + // set WebGL's render context (number of pixels to draw) + this.#gl.viewport(0, 0, this.#gl.canvas.width, this.#gl.canvas.height); + + // set defaults + this.setMaxFps(30); + this.setClearColor(0., 0., 0., 255.); + } + + static #getWebGlContext(canvas) { + try { + return canvas.getContext("webgl") ?? canvas.getContext("experimental-webgl"); + } catch { + return null; + }; + } + + static isWebGlSupported() { + try { + const canvas = document.createElement('canvas'); + return !!window.WebGLRenderingContext && Smc.#getWebGlContext(canvas) != null; + } catch (e) { + return false; + } + } + + onError(delegate) { + this.#errorDelegate = delegate; + return this; + } + + onInit(delegate) { + this.#initDelegate = delegate; + return this; + } + + onResize(delegate) { + this.#resizeDelegate = delegate; + return this; + } + + setClearColorHex(color) { + color = hexToRgba(color); + if (color == null) { + // this.raiseError isn't needed because this should + // be treated as a "compilation" error not a "runtime" error + throw new Error(`setClearColorHex expects an RGB/RGBA hex value, got "${color}"`); + } + return this.setClearColor(color.r, color.g, color.b, color.a); + } + + setClearColor(r, g, b, a) { + this.#gl.clearColor(r / 255., g / 255., b / 255., a / 255.); + return this; + } + + setVertices(positions) { + this.#vertices = positions; + return this; + } + + setMaxFps(fps) { + this.#maxFps = fps; + this.#minDeltaTimeMs = fps ? 1000 / fps : null; + return this; + } + + setProgram(delegate) { + const builder = new SmcProgramBuilder(this.#gl, this.raiseError); + delegate(builder); // i pray js passes by ref well... + + this.#program = builder.build(); + if (!this.#gl.getProgramParameter(this.#program, this.#gl.LINK_STATUS)) { + const infoLog = this.#gl.getProgramInfoLog(this.#program); + this.raiseError( + SmcErr.PROGRAM_INIT, + new Error(`Unable to initialize the shader program: ${infoLog}`) + ) + } + + this.#addAttribute("aVertex", this.#setVerticesAttribute.bind(this)); + // DEBUG: uncomment afterwards + // this.#addUniform("uResolution", UniformType.Float2); + // this.#addUniform("uTime", UniformType.Float1); + // this.#addUniform("uDelta", UniformType.Float1); + return this; + } + + run() { + this.#initDelegate() + this.setAttribute("aVertex", this.#vertices); + // DEBUG: uncomment afterwards + // this.setUniform("uResolution", new Float32Array([this.#gl.canvas.width, this.#gl.canvas.height])); + + if (this.#maxFps == 0) + requestAnimationFrame(this.render) + else + requestAnimationFrame(this.renderLoop); + } + + // requestAnimationFrame requests the browser to call the renderLoop + // callback function before the next repaint. + // `time` is the milliseconds elapsed since the page loaded. + renderLoop(time) { + var delta = time - this.#prevTimeMs; + this.render(time, delta); + + setTimeout( + () => requestAnimationFrame(this.renderLoop), + Math.max(0, delta - this.#minDeltaTimeMs) + ); + this.#prevTimeMs = time; + } + + render(time, delta) { + // DEBUG: uncomment afterwards + // this.setUniform("uTime", time * 0.001); + // this.setUniform("uDelta", delta); + + // DEBUG: START (remove if not necessary) + this.#gl.viewport(0, 0, this.#gl.canvas.width, this.#gl.canvas.height); + this.#gl.clear(this.#gl.COLOR_BUFFER_BIT | this.#gl.DEPTH_BUFFER_BIT); + + this.setAttribute("aVertex", this.#vertices); + + this.#gl.useProgram(this.#program); + + // DEBUG: uncomment afterwards + // this.setUniform("uTime", time * 0.001); + // this.setUniform("uDelta", delta); + // this.setUniform("uResolution", new Float32Array([this.#gl.canvas.width, this.#gl.canvas.height])); + + this.#gl.drawArrays(this.#gl.TRIANGLE_STRIP, 0, this.#vertices.length / 2); + // DEBUG: END (remove if not necessary) + + // DEBUG: uncomment afterwards + // this.#gl.clear(this.#clearBitFlags); + // this.#gl.drawArrays(this.#gl.TRIANGLE_STRIP, 0, this.#vertices.length); + } + + #addAttribute(name, setDelegate, required = false) { + var location = this.#gl.getAttribLocation(this.#program, name); + if (location == -1) { + if (required) { + this.raiseError( + SmcErr.ATTRIBUTE_MISSING, + `Linked program missing required attribute: "${name}"` + ); + } + location = null; + } + this.#attributes.set( + name, + { + setDelegate: setDelegate, + location: location, + }); + + } + + #addUniform(name, type, setEachFrame, setCallback, required = false) { + const location = this.#gl.getUniformLocation(this.#program, name); + if (location == -1) { + if (required) { + this.raiseError( + SmcErr.UNIFORM_MISSING, + `Linked program missing required uniform: "${name}"` + ) + } + location = null; + } + + if (type == UniformType.Float1) + var uniformfv = this.#gl.uniform1f; + else if (type == UniformType.Float2) + var uniformfv = this.#gl.uniform2fv; + else if (type == UniformType.Float3) + var uniformfv = this.#gl.uniform3fv; + else if (type == UniformType.Float4) + var uniformfv = this.#gl.uniform4fv; + else if (type == UniformType.Int1) + var uniformfv = this.#gl.uniform1i; + else if (type == UniformType.Int2) + var uniformfv = this.#gl.uniform2iv; + else if (type == UniformType.Int3) + var uniformfv = this.#gl.uniform3iv; + else if (type == UniformType.Int4) + var uniformfv = this.#gl.uniform4iv; + else { + // this.raiseError isn't needed because this should + // be treated as a "compilation" error not a "runtime" error + throw new Error(`Expected type from enum UniformType, but got "${type}"`); + } + + const setDelegate = value => uniformfv(location, value); + + // simplify function call to a single argument + this.#uniforms.set( + name, + { + setDelegate: setDelegate, + location: location, + setEachFrame: setEachFrame, + setCallback, + } + ); + } + + #getAttributeLocation(name) { + return this.#attributes.get(name).location; + } + + #getUniformLocation(name) { + return this.#uniforms.get(name).location; + } + + setAttribute(name, value) { + if (this.#getAttributeLocation(name) != null) + this.#attributes.get(name).setDelegate(value); + } + + setUniform(name, value) { + if (this.#getUniformLocation(name) != null) + this.#uniforms.get(name).setDelegate(value); + } + + #setVerticesAttribute(vertices) { + this.#vertices = vertices; + + const buffer = this.#gl.createBuffer(); + this.#gl.bindBuffer(this.#gl.ARRAY_BUFFER, buffer); + this.#gl.bufferData( + this.#gl.ARRAY_BUFFER, + new Float32Array(vertices), + this.#gl.STATIC_DRAW + ); + + this.#gl.vertexAttribPointer( + this.#getAttributeLocation("aVertex"), + 2, // (size) one vertex == 2 floats + this.#gl.FLOAT, // (type) vertex positions given as 32bit floats + false, // (normalized) don't normalize + 0, // (stride) buffer offset pointer BETWEEN elements (0 => packed) + 0, // (offset) buffer offset pointer from START to first element + ) + this.#gl.enableVertexAttribArray(this.#getAttributeLocation("aVertex")); + + return buffer; + } + + raiseError(type, error) { + this.#errorDelegate(type, error); + } +} diff --git a/www/js/smc/smc.js.bak b/www/js/smc/smc.js.bak new file mode 100644 index 0000000..a542864 --- /dev/null +++ b/www/js/smc/smc.js.bak @@ -0,0 +1,37 @@ +import { SmcErr } from "./errors.js"; +import { SmcBuilder } from "./builder.js"; + +export { SmcErr }; + +// XXX: TODO: merge SmcBuilder into smc +class smc { + #canvas; + #builderDelegate = _ => { }; + + constructor(canvas) { + this.#canvas = canvas; + } + + build(delegate) { + this.#builderDelegate = delegate; + return this; + } + + onError(delegate) { + this.#errorDelegate = delegate; + return this; + } + + run() { + this.#canvas = canvas; + const gl = this.#canvas.getContext("webgl"); + if (gl == null) { + this.#raiseError( + SmcErr.UNSUPPORTED, + Error("Unable to initialize WebGL. Your browser or machine may not support it."), + ); + } + const builder = this.#builderDelegate(new SmcBuilder(gl, this.#raiseError)) + builder.render() + } +} diff --git a/www/js/smc/util.js b/www/js/smc/util.js new file mode 100644 index 0000000..a786d02 --- /dev/null +++ b/www/js/smc/util.js @@ -0,0 +1,24 @@ +export { hexToRgba, hexToRgbaNormal }; + +/* Converts a string of the form "#XXXXXX" + * +*/ +function hexToRgba(hex) { + var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i.exec(hex.toLowerCase()); + return result ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16), + a: result.length == 4 ? parseInt(result[4], 16) : 255., + } : null; +} + +function hexToRgbaNormal(hex) { + var result = hexToRgba(hex); + return result ? { + r: result.r / 255., + g: result.g / 255., + b: result.b / 255., + a: result.a / 255., + } : null; +} From 3f28498ad4fa8eee82e50e178664d4f20968899c Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Mon, 2 Feb 2026 03:43:54 +1000 Subject: [PATCH 06/11] IT'S ALIVE --- www/js/smc/progbuilder.js | 15 ++++++--- www/js/smc/smc.js | 68 ++++++++++++++------------------------- 2 files changed, 36 insertions(+), 47 deletions(-) diff --git a/www/js/smc/progbuilder.js b/www/js/smc/progbuilder.js index da86049..9da0bd5 100644 --- a/www/js/smc/progbuilder.js +++ b/www/js/smc/progbuilder.js @@ -23,17 +23,24 @@ class SmcProgramBuilder { precision mediump float; // uniform float uTime; - // uniform vec2 uResolution; + uniform vec2 uResolution; void main() { - // vec2 uv = gl_FragCoord.xy / uResolution; + vec2 uv = gl_FragCoord.xy / uResolution.xy; // vec3 col = 0.5 + 0.5 * cos(uTime + uv.xyx + vec3(0, 2, 4)); // gl_FragColor = vec4(col, 1.0); // gl_FragColor = vec4(216., 43., 72., 255.) / 255.; - float maxfc = max(gl_FragCoord.x, gl_FragCoord.y); - gl_FragColor = vec4(gl_FragCoord.xy, maxfc, maxfc) / maxfc; + // float maxfc = max(gl_FragCoord.x, gl_FragCoord.y); + // gl_FragColor = vec4(gl_FragCoord.xy, maxfc, maxfc) / maxfc; + + float maxuv = max(uv.x, uv.y); + gl_FragColor = vec4(216. * maxuv, 43., 72., 255.) / 255.; + + + // vec3 col = 0.5 + 0.5*cos(uv.xyx+vec3(0,2,4)); + // gl_FragColor = vec4(col, 1.); } `; diff --git a/www/js/smc/smc.js b/www/js/smc/smc.js index 2f77955..71f0c5d 100644 --- a/www/js/smc/smc.js +++ b/www/js/smc/smc.js @@ -46,6 +46,9 @@ class Smc { this.raiseError = this.raiseError.bind(this); this.render = this.render.bind(this); this.renderLoop = this.renderLoop.bind(this); + // DEBUG: is this necessary + this.setAttribute = this.setAttribute.bind(this); + this.setUniform = this.setUniform.bind(this); this.#canvas = canvas; this.#gl = Smc.#getWebGlContext(canvas); @@ -137,17 +140,16 @@ class Smc { this.#addAttribute("aVertex", this.#setVerticesAttribute.bind(this)); // DEBUG: uncomment afterwards - // this.#addUniform("uResolution", UniformType.Float2); - // this.#addUniform("uTime", UniformType.Float1); - // this.#addUniform("uDelta", UniformType.Float1); + this.#addUniform("uResolution", UniformType.Float2, false, (_) => new Float32Array([this.#gl.canvas.width, this.#gl.canvas.height])); + this.#addUniform("uTime", UniformType.Float1); + this.#addUniform("uDelta", UniformType.Float1); return this; } run() { this.#initDelegate() this.setAttribute("aVertex", this.#vertices); - // DEBUG: uncomment afterwards - // this.setUniform("uResolution", new Float32Array([this.#gl.canvas.width, this.#gl.canvas.height])); + this.setUniform("uResolution", this.#gl.canvas.width, this.#gl.canvas.height); if (this.#maxFps == 0) requestAnimationFrame(this.render) @@ -170,29 +172,10 @@ class Smc { } render(time, delta) { - // DEBUG: uncomment afterwards - // this.setUniform("uTime", time * 0.001); - // this.setUniform("uDelta", delta); - - // DEBUG: START (remove if not necessary) - this.#gl.viewport(0, 0, this.#gl.canvas.width, this.#gl.canvas.height); + this.setUniform("uTime", time * 0.001); + this.setUniform("uDelta", delta); this.#gl.clear(this.#gl.COLOR_BUFFER_BIT | this.#gl.DEPTH_BUFFER_BIT); - - this.setAttribute("aVertex", this.#vertices); - - this.#gl.useProgram(this.#program); - - // DEBUG: uncomment afterwards - // this.setUniform("uTime", time * 0.001); - // this.setUniform("uDelta", delta); - // this.setUniform("uResolution", new Float32Array([this.#gl.canvas.width, this.#gl.canvas.height])); - this.#gl.drawArrays(this.#gl.TRIANGLE_STRIP, 0, this.#vertices.length / 2); - // DEBUG: END (remove if not necessary) - - // DEBUG: uncomment afterwards - // this.#gl.clear(this.#clearBitFlags); - // this.#gl.drawArrays(this.#gl.TRIANGLE_STRIP, 0, this.#vertices.length); } #addAttribute(name, setDelegate, required = false) { @@ -228,37 +211,35 @@ class Smc { } if (type == UniformType.Float1) - var uniformfv = this.#gl.uniform1f; + var uniformfv = (...values) => this.#gl.uniform1f(location, ...values); else if (type == UniformType.Float2) - var uniformfv = this.#gl.uniform2fv; + var uniformfv = (...values) => this.#gl.uniform2f(location, ...values); else if (type == UniformType.Float3) - var uniformfv = this.#gl.uniform3fv; + var uniformfv = (...values) => this.#gl.uniform3f(location, ...values); else if (type == UniformType.Float4) - var uniformfv = this.#gl.uniform4fv; + var uniformfv = (...values) => this.#gl.uniform4f(location, ...values); else if (type == UniformType.Int1) - var uniformfv = this.#gl.uniform1i; + var uniformfv = (...values) => this.#gl.uniform1i(location, ...values); else if (type == UniformType.Int2) - var uniformfv = this.#gl.uniform2iv; + var uniformfv = (...values) => this.#gl.uniform2i(location, ...values); else if (type == UniformType.Int3) - var uniformfv = this.#gl.uniform3iv; + var uniformfv = (...values) => this.#gl.uniform3i(location, ...values); else if (type == UniformType.Int4) - var uniformfv = this.#gl.uniform4iv; + var uniformfv = (...values) => this.#gl.uniform4i(location, ...values); else { // this.raiseError isn't needed because this should // be treated as a "compilation" error not a "runtime" error throw new Error(`Expected type from enum UniformType, but got "${type}"`); } - const setDelegate = value => uniformfv(location, value); - // simplify function call to a single argument this.#uniforms.set( name, { - setDelegate: setDelegate, + setDelegate: uniformfv, location: location, setEachFrame: setEachFrame, - setCallback, + setCallback: setCallback, } ); } @@ -271,14 +252,15 @@ class Smc { return this.#uniforms.get(name).location; } - setAttribute(name, value) { + setAttribute(name, ...args) { if (this.#getAttributeLocation(name) != null) - this.#attributes.get(name).setDelegate(value); + this.#attributes.get(name).setDelegate(...args); } - setUniform(name, value) { - if (this.#getUniformLocation(name) != null) - this.#uniforms.get(name).setDelegate(value); + setUniform(name, ...args) { + if (this.#getUniformLocation(name) != null) { + this.#uniforms.get(name).setDelegate(...args); + } } #setVerticesAttribute(vertices) { From 618a3aec2325ac4d8ea73b1f85b8a0444f45a76e Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Mon, 2 Feb 2026 03:44:43 +1000 Subject: [PATCH 07/11] remove *.bak --- www/js/smc/draw-scene.js.bak | 60 ------------------ www/js/smc/init-buffers.js.bak | 34 ---------- www/js/smc/lib.js.bak | 112 --------------------------------- www/js/smc/smc.js.bak | 37 ----------- 4 files changed, 243 deletions(-) delete mode 100644 www/js/smc/draw-scene.js.bak delete mode 100644 www/js/smc/init-buffers.js.bak delete mode 100644 www/js/smc/lib.js.bak delete mode 100644 www/js/smc/smc.js.bak diff --git a/www/js/smc/draw-scene.js.bak b/www/js/smc/draw-scene.js.bak deleted file mode 100644 index 0df76cc..0000000 --- a/www/js/smc/draw-scene.js.bak +++ /dev/null @@ -1,60 +0,0 @@ -function drawScene(gl, programInfo, buffers, time) { - // Tell WebGL how to convert from clip space to pixels - gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); - - gl.clearColor(0.0, 0.0, 0.0, 1.0); // Clear to black, fully opaque - gl.clearDepth(1.0); // Clear everything - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - - // NOTE: this is how width/height is taken - // const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight; - - // Tell WebGL how to pull out the positions from the position - // buffer into the vertexPosition attribute. - setPositionAttribute(gl, buffers, programInfo); - - gl.useProgram(programInfo.program); - - /* --- Set Uniform Variables --- */ - // Time since page loaded in seconds - gl.uniform1f( - programInfo.uniformLocations.time, - time, - ); - // Viewport resolution in pixels - gl.uniform2f( - programInfo.uniformLocations.resolution, - gl.canvas.width, - gl.canvas.height, - ); - - { - const offset = 0; - const vertexCount = 4; - gl.drawArrays(gl.TRIANGLE_STRIP, offset, vertexCount); - } -} - -// Tell WebGL how to pull out the positions from the position -// buffer into the vertexPosition attribute. -function setPositionAttribute(gl, buffers, programInfo) { - const numComponents = 2; // pull out 2 values per iteration - const type = gl.FLOAT; // the data in the buffer is 32bit floats - const normalize = false; // don't normalize - const stride = 0; // how many bytes to get from one set of values to the next - // 0 = use type and numComponents above - const offset = 0; // how many bytes inside the buffer to start from - gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position); - gl.vertexAttribPointer( - programInfo.attribLocations.vertexPosition, - numComponents, - type, - normalize, - stride, - offset, - ); - gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition); -} - -export { drawScene }; - diff --git a/www/js/smc/init-buffers.js.bak b/www/js/smc/init-buffers.js.bak deleted file mode 100644 index bf12c1a..0000000 --- a/www/js/smc/init-buffers.js.bak +++ /dev/null @@ -1,34 +0,0 @@ -function initBuffers(gl) { - const positionBuffer = initPositionBuffer(gl); - - return { - position: positionBuffer, - }; -} - -function initPositionBuffer(gl) { - // Position array of a "full-screen" quad (encoded as TRIANGLE_STRIP) - // Ref: https://en.wikipedia.org/wiki/Triangle_strip - // NOTE: +x,+y is top-right & -x,-y is bottom-left - const positions = [ - -1.0, 1.0, - -1.0, -1.0, - 1.0, 1.0, - 1.0, -1.0, - ]; - - // Create a buffer for the square's positions. - const positionBuffer = gl.createBuffer(); - // Select the positionBuffer as the one to apply buffer - // operations to from here out. - gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); - // Now pass the list of positions into WebGL to build the - // shape. We do this by creating a Float32Array from the - // JavaScript array, then use it to fill the current buffer. - gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); - - return positionBuffer; -} - -export { initBuffers }; - diff --git a/www/js/smc/lib.js.bak b/www/js/smc/lib.js.bak deleted file mode 100644 index 4f08661..0000000 --- a/www/js/smc/lib.js.bak +++ /dev/null @@ -1,112 +0,0 @@ -import { initBuffers } from "./init-buffers.js"; -import { drawScene } from "./draw-scene.js"; - -export { run }; - -// Initialize a shader program, so WebGL knows how to draw our data -function initShaderProgram(gl, vsSource, fsSource) { - const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource); - const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource); - - // Create the shader program - const program = gl.createProgram(); - gl.attachShader(program, vertexShader); - gl.attachShader(program, fragmentShader); - gl.linkProgram(program); - - // If creating the shader program failed, alert - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { - alert( - `Unable to initialize the shader program: ${gl.getProgramInfoLog( - program, - )}`, - ); - return null; - } - - return program; -} - -function renderShader(gl, vsSource, fsSource) { - const shaderProgram = initShaderProgram(gl, vsSource, fsSource); - - // Collect all the info needed to use the shader program. - // Look up which attribute our shader program is using - // for aVertexPosition and look up uniform locations. - const programInfo = { - program: shaderProgram, - attribLocations: { - vertexPosition: gl.getAttribLocation(shaderProgram, "aVertexPosition"), - }, - uniformLocations: { - resolution: gl.getUniformLocation(shaderProgram, "u_resolution"), - time: gl.getUniformLocation(shaderProgram, "u_time"), - }, - }; - - // Here's where we call the routine that builds all the - // objects we'll be drawing. - const buffers = initBuffers(gl); - - const fpsLimit = 30; - const fpsDelta = 1000 / fpsLimit; - // let timePrev = 0; - // requestAnimationFrame asks the browser to call render, - // providing the time in milliseconds since the page loaded - function render(time) { - time *= 0.001; // convert to seconds - // delta = time - timePrev; - - drawScene(gl, programInfo, buffers, time); - - setTimeout(() => requestAnimationFrame(render), fpsDelta); - } - function update() { - requestAnimationFrame(render); - } - - // XXX: TODO: read this guide it's great! https://stackoverflow.com/questions/56998225/why-is-rendering-blurred-in-webgl - // window.addEventListener('resize', render); - - requestAnimationFrame(render); - // update(); - // setInterval(update, 1000 / fpsLimit); -} - -function fetchShader(name) { - return fetch(`../shaders/${name}`) - .then(res => { - if (!res.ok) throw new Error(`Failed to load fragment shader source ${url}: ${res.status}`); - return res.text(); - }); - -} - -function run(canvas) { - const gl = canvas.getContext("webgl"); - - // XXX: TODO: use `window.addEventListener('resize', ...);` - canvas.setAttribute('width', window.innerWidth); - canvas.setAttribute('height', window.innerHeight); - - // Only continue if WebGL is available and working - if (gl === null) { - throw new Error("Unable to initialize WebGL. Your browser or machine may not support it."); - } - - // Vertex shader program - const vsSource = ` - attribute vec4 aVertexPosition; - - void main() { - gl_Position = aVertexPosition; - } - `; - - // Fetch fragment shader program - fetchShader("fbm.glsl") - .then(fsSource => { - renderShader(gl, vsSource, fsSource); - }); -} - diff --git a/www/js/smc/smc.js.bak b/www/js/smc/smc.js.bak deleted file mode 100644 index a542864..0000000 --- a/www/js/smc/smc.js.bak +++ /dev/null @@ -1,37 +0,0 @@ -import { SmcErr } from "./errors.js"; -import { SmcBuilder } from "./builder.js"; - -export { SmcErr }; - -// XXX: TODO: merge SmcBuilder into smc -class smc { - #canvas; - #builderDelegate = _ => { }; - - constructor(canvas) { - this.#canvas = canvas; - } - - build(delegate) { - this.#builderDelegate = delegate; - return this; - } - - onError(delegate) { - this.#errorDelegate = delegate; - return this; - } - - run() { - this.#canvas = canvas; - const gl = this.#canvas.getContext("webgl"); - if (gl == null) { - this.#raiseError( - SmcErr.UNSUPPORTED, - Error("Unable to initialize WebGL. Your browser or machine may not support it."), - ); - } - const builder = this.#builderDelegate(new SmcBuilder(gl, this.#raiseError)) - builder.render() - } -} From 1683d2bbe9918f1e3ad7a84cc5c19b162c4d004c Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Mon, 2 Feb 2026 05:02:18 +1000 Subject: [PATCH 08/11] outsource async fetching --- www/js/main.js | 29 +++++++++++++++++++++-------- www/js/smc/progbuilder.js | 39 +++++++++++++++++++-------------------- www/js/smc/smc.js | 4 ++-- 3 files changed, 42 insertions(+), 30 deletions(-) diff --git a/www/js/main.js b/www/js/main.js index a2aa9b7..a5ca67b 100644 --- a/www/js/main.js +++ b/www/js/main.js @@ -2,18 +2,31 @@ import { Smc } from "./smc/smc.js" main(); +async function fetchShader(uri, delegate) { + const res = await fetch(uri); + if (res.ok) + return await res.text(); + this.raiseError( + SmcErr.FETCH_SHADER, + `Failed to load shader source ${url}: ${res.status} ${res.json()}`); + return "" +} + + function main() { const canvas = document.querySelector("#gl-canvas"); canvas.setAttribute('width', window.innerWidth); canvas.setAttribute('height', window.innerHeight); - new Smc(canvas) - .setMaxFps(30) - .setProgram(builder => - builder - // .fetchVertexShader("../shaders/segfault.glsl") - // .fetchFragmentShader("../shaders/segfault.glsl")) - ) - .run(); + + fetchShader("../shaders/segfault.glsl") + .then(frag => + new Smc(canvas) + .setMaxFps(30) + .setProgram(builder => + builder + .addFragmentShader(frag)) + .run() + ); } diff --git a/www/js/smc/progbuilder.js b/www/js/smc/progbuilder.js index 9da0bd5..6084410 100644 --- a/www/js/smc/progbuilder.js +++ b/www/js/smc/progbuilder.js @@ -71,19 +71,21 @@ class SmcProgramBuilder { source ) ) + console.log(source) this.#hasFragmentShader = true; return this; } - fetchVertexShader(uri) { - this.#fetchShader(uri, (source) => this.addVertexShader(source)); - return this; - } + // fetchVertexShader(uri) { + // (async () => this.#fetchShader(uri, (source) => this.addVertexShader(source)))(); + // return this; + // } - fetchFragmentShader(uri) { - this.#fetchShader(uri, (source) => this.addFragmentShader(source)); - return this; - } + // async fetchFragmentShader(uri) { + // var delegate = (source) => this.addFragmentShader(source); + // var source = await this.#fetchShader(uri); + // return this; + // } build() { // avoid user accidental calls to build() @@ -117,18 +119,15 @@ class SmcProgramBuilder { return shader; } - #fetchShader(uri, delegate) { - return fetch(uri) - .then(res => { - if (res.ok) - delegate(res.text()); - else { - this.raiseError( - SmcErr.FETCH_SHADER, - `Failed to load shader source ${url}: ${res.status} ${res.json()}`); - } - }); - } + // async #fetchShader(uri, delegate) { + // const res = await fetch(uri); + // if (res.ok) + // return await res.text(); + // this.raiseError( + // SmcErr.FETCH_SHADER, + // `Failed to load shader source ${url}: ${res.status} ${res.json()}`); + // return "" + // } } diff --git a/www/js/smc/smc.js b/www/js/smc/smc.js index 71f0c5d..9829921 100644 --- a/www/js/smc/smc.js +++ b/www/js/smc/smc.js @@ -127,9 +127,9 @@ class Smc { setProgram(delegate) { const builder = new SmcProgramBuilder(this.#gl, this.raiseError); - delegate(builder); // i pray js passes by ref well... + var result = delegate(builder); - this.#program = builder.build(); + this.#program = result.build(); if (!this.#gl.getProgramParameter(this.#program, this.#gl.LINK_STATUS)) { const infoLog = this.#gl.getProgramInfoLog(this.#program); this.raiseError( From 53a62f639eb3b1802650c1b4be1c55cb2c7bc601 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Mon, 2 Feb 2026 05:02:23 +1000 Subject: [PATCH 09/11] rename uniforms --- www/shaders/fbm.glsl | 15 +++++++-------- www/shaders/optimised.glsl | 18 +++++++++--------- www/shaders/sample.glsl | 23 +++++++++++++++++++++++ www/shaders/segfault.glsl | 28 ++++++++++++++-------------- www/shaders/trivial.glsl | 12 ++++++------ 5 files changed, 59 insertions(+), 37 deletions(-) create mode 100644 www/shaders/sample.glsl diff --git a/www/shaders/fbm.glsl b/www/shaders/fbm.glsl index b35ba8f..dcb1f4d 100644 --- a/www/shaders/fbm.glsl +++ b/www/shaders/fbm.glsl @@ -5,9 +5,8 @@ precision mediump float; #endif -uniform vec2 u_resolution; -uniform vec2 u_mouse; -uniform float u_time; +uniform vec2 uResolution; +uniform float uTime; float random (in vec2 _st) { return fract(sin(dot(_st.xy, @@ -65,17 +64,17 @@ float fbm ( in vec2 _st) { #define SCALE 3. void main() { - vec2 st = gl_FragCoord.xy/u_resolution.xy*SCALE; - // st += st * abs(sin(u_time*0.1)*3.0); + vec2 st = gl_FragCoord.xy/uResolution.xy*SCALE; + // st += st * abs(sin(uTime*0.1)*3.0); vec2 q = vec2( - fbm( st + 0.*u_time), + fbm( st + 0.*uTime), fbm( st + vec2(1.0)) ); vec2 r = vec2( - fbm( st + 1.0*q + vec2(1.7,9.2)+ 0.15*u_time ), - fbm( st + 1.0*q + vec2(8.3,2.8)+ 0.126*u_time) + fbm( st + 1.0*q + vec2(1.7,9.2)+ 0.15*uTime ), + fbm( st + 1.0*q + vec2(8.3,2.8)+ 0.126*uTime) ); float f = fbm(st+r); diff --git a/www/shaders/optimised.glsl b/www/shaders/optimised.glsl index 9d08431..8fd8d1b 100644 --- a/www/shaders/optimised.glsl +++ b/www/shaders/optimised.glsl @@ -13,8 +13,8 @@ precision mediump float; # endif #endif -uniform float u_time; -uniform vec2 u_resolution; +uniform float uTime; +uniform vec2 uResolution; /* ==== Text Colouring ==== */ #define PHOSPHOR_COL vec4(196./255., 167./255., 231./255., 1.) @@ -49,9 +49,9 @@ float roundLine(vec2 p, vec2 a, vec2 b) { b -= a + vec2(1.0,0.); p -= a; float f = length(p-clamp(dot(p,b)/dot(b,b),0.0,1.0)*b); - if (u_resolution.y < 320.) // attempt to get rid of aliasing on small resolution + if (uResolution.y < 320.) // attempt to get rid of aliasing on small resolution return smoothstep(1.0, 0.9, f); - else if (u_resolution.y < 720.) + else if (uResolution.y < 720.) return smoothstep(0.75, 0.5, f); else return smoothstep(1., 0., f); @@ -196,7 +196,7 @@ float vt220Font(vec2 p, float c) { // https://www.shadertoy.com/view/llSXDV (same author as VT220 font rendering) // float textLines(vec2 uvG) { - float wt = 5. * (u_time + 0.5*sin(u_time*1.4) + 0.2*sin(u_time*2.9)); // wobbly time + float wt = 5. * (uTime + 0.5*sin(uTime*1.4) + 0.2*sin(uTime*2.9)); // wobbly time vec2 uvGt = uvG + vec2(0., floor(wt)); float ll = rand(vec2(uvGt.y, - 1.)) * ROWCOLS.x; // line length @@ -275,7 +275,7 @@ float fbm( in vec2 _st) { // XXX: TODO: use two shaders, the first on a MUCH lower resolution (one pixel for each text character) // XXX: TODO: then the second should map those pixel values to higher resolution text characters void main() { - vec2 uv = gl_FragCoord.xy / u_resolution; + vec2 uv = gl_FragCoord.xy / uResolution; vec2 st = uv * SCALE; // // uvNoise = ceil(uvNoise * ROWCOLS) / ROWCOLS; @@ -295,13 +295,13 @@ void main() { // gl_FragColor = vt220Font(uvT - uvG * FONT_SIZE, val) * PHOSPHOR_COL + BG_COL; vec2 q = vec2( - fbm( st + 0.*u_time), + fbm( st + 0.*uTime), fbm( st + vec2(1.0)) ); vec2 r = vec2( - fbm( st + 1.0*q + vec2(1.7,9.2)+ 0.15*u_time ), - fbm( st + 1.0*q + vec2(8.3,2.8)+ 0.126*u_time) + fbm( st + 1.0*q + vec2(1.7,9.2)+ 0.15*uTime ), + fbm( st + 1.0*q + vec2(8.3,2.8)+ 0.126*uTime) ); float f = fbm(st+r); diff --git a/www/shaders/sample.glsl b/www/shaders/sample.glsl new file mode 100644 index 0000000..9b54b8f --- /dev/null +++ b/www/shaders/sample.glsl @@ -0,0 +1,23 @@ +precision mediump float; + +// uniform float uTime; +uniform vec2 uResolution; + +void main() { + vec2 uv = gl_FragCoord.xy / uResolution.xy; + // vec3 col = 0.5 + 0.5 * cos(uTime + uv.xyx + vec3(0, 2, 4)); + // gl_FragColor = vec4(col, 1.0); + // gl_FragColor = vec4(216., 43., 72., 255.) / 255.; + + + // float maxfc = max(gl_FragCoord.x, gl_FragCoord.y); + // gl_FragColor = vec4(gl_FragCoord.xy, maxfc, maxfc) / maxfc; + + float maxuv = max(uv.x, uv.y); + gl_FragColor = vec4(34., 43., 192.*maxuv, 255.) / 255.; + + + // vec3 col = 0.5 + 0.5*cos(uv.xyx+vec3(0,2,4)); + // gl_FragColor = vec4(col, 1.); +} + diff --git a/www/shaders/segfault.glsl b/www/shaders/segfault.glsl index adabb14..a96bb19 100644 --- a/www/shaders/segfault.glsl +++ b/www/shaders/segfault.glsl @@ -11,8 +11,8 @@ precision mediump float; #endif -uniform float u_time; -uniform vec2 u_resolution; +uniform float uTime; +uniform vec2 uResolution; /* ==== Text Colouring ==== */ #define PHOSPHOR_COL vec4(196./255., 167./255., 231./255., 1.) @@ -155,9 +155,9 @@ float roundLine(vec2 p, vec2 a, vec2 b) { b -= a + vec2(1.0,0.); p -= a; float f = length(p-clamp(dot(p,b)/dot(b,b),0.0,1.0)*b); - if (u_resolution.y < 320.) // attempt to get rid of aliasing on small resolution + if (uResolution.y < 320.) // attempt to get rid of aliasing on small resolution return smoothstep(1.0, 0.9, f); - else if (u_resolution.y < 720.) + else if (uResolution.y < 720.) return smoothstep(0.75, 0.5, f); else return smoothstep(1., 0., f); @@ -305,7 +305,7 @@ float vt220Font(vec2 p, float c) { // https://www.shadertoy.com/view/MsdGWn // float textLines(vec2 uvG) { - float wt = 5. * (u_time + 0.5*sin(u_time*1.4) + 0.2*sin(u_time*2.9)); // wobbly time + float wt = 5. * (uTime + 0.5*sin(uTime*1.4) + 0.2*sin(uTime*2.9)); // wobbly time vec2 uvGt = uvG + vec2(0., floor(wt)); float ll = rand(vec2(uvGt.y, - 1.)) * ROWCOLS.x; // line length @@ -351,21 +351,21 @@ float smokeNoise(vec3 v) { } void main() { - vec2 uv = vec2(gl_FragCoord.x, u_resolution.y - gl_FragCoord.y); - vec2 uvT = ROWCOLS * FONT_SIZE * uv / u_resolution.xy; - vec2 uvG = floor(ROWCOLS * uv / u_resolution.xy); - vec2 uvC = gl_FragCoord.xy / u_resolution.xy; + vec2 uv = vec2(gl_FragCoord.x, uResolution.y - gl_FragCoord.y); + vec2 uvT = ROWCOLS * FONT_SIZE * uv / uResolution.xy; + vec2 uvG = floor(ROWCOLS * uv / uResolution.xy); + vec2 uvC = gl_FragCoord.xy / uResolution.xy; - vec2 uvNoise = gl_FragCoord.xy / u_resolution.xy; + vec2 uvNoise = gl_FragCoord.xy / uResolution.xy; uvNoise = ceil(uvNoise * ROWCOLS) / ROWCOLS; float val; - if (u_time < 2.0) + if (uTime < 2.0) val = textLines(uvG); - else if (u_time < 2.3) - val = rand(uvG * u_time) * 17.; + else if (uTime < 2.3) + val = rand(uvG * uTime) * 17.; else { - float noise = smokeNoise(vec3(uvNoise * noiseScale, u_time * noiseTimeScale)); + float noise = smokeNoise(vec3(uvNoise * noiseScale, uTime * noiseTimeScale)); // Noise is fed through a sigmoid function, then quantised to integer range 0-17 val = (exp(noise) / 2.71828); // increase contrast (normalised 0.0 - 1.0) val = 1.0 / val; diff --git a/www/shaders/trivial.glsl b/www/shaders/trivial.glsl index 22464ae..9935c91 100644 --- a/www/shaders/trivial.glsl +++ b/www/shaders/trivial.glsl @@ -1,17 +1,17 @@ // is highp wasteful for this shader? #ifdef GL_FRAGMENT_PRECISION_HIGH - precision highp float; +precision highp float; #else - precision mediump float; +precision mediump float; #endif -uniform float u_time; -uniform vec2 u_resolution; +uniform float uTime; +uniform vec2 uResolution; void main() { - vec2 uv = gl_FragCoord.xy / u_resolution; + vec2 uv = gl_FragCoord.xy / uResolution; - vec3 col = 0.5 + 0.5 * cos(u_time + uv.xyx + vec3(0, 2, 4)); + vec3 col = 0.5 + 0.5 * cos(uTime + uv.xyx + vec3(0, 2, 4)); gl_FragColor = vec4(col,1.0); } From 7defe8ae5066eaa70ad9d30780950205a5b08837 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Tue, 3 Feb 2026 10:09:42 +1000 Subject: [PATCH 10/11] add squares.glsl --- www/shaders/squares.glsl | 50 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 www/shaders/squares.glsl diff --git a/www/shaders/squares.glsl b/www/shaders/squares.glsl new file mode 100644 index 0000000..3520ecc --- /dev/null +++ b/www/shaders/squares.glsl @@ -0,0 +1,50 @@ +// is highp wasteful for this shader? +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else +precision mediump float; +#endif + +uniform float uTime; +uniform vec2 uResolution; + +#define ROWS 10. + +float rand(in vec2 _st) { + return fract(sin(dot(_st.xy, vec2(12.9898,78.233))) * 43758.5453123); +} + +// #define STEPS 10. + +void main() { + // float ROWS = mod(uTime, 2. * STEPS); + + float aspect = uResolution.x / uResolution.y; + float cols = floor(ROWS * aspect); + + vec2 uv = vec2( + ceil(gl_FragCoord.x / uResolution.x * cols) / cols, + ceil(gl_FragCoord.y / uResolution.y * ROWS) / ROWS + ); + // vec2 uv = ceil(gl_FragCoord.xy / uResolution * vec2(ROWS, COLS)) / vec2(ROWS, COLS); + + float offset = rand(uv) + rand(vec2(uTime, uTime)); + float id = mod(abs(uv.y * ROWS + uv.x * cols + offset), 4.); // project f(t) = (1, 1)t + uv against the y-axis + vec3 col = vec3(255., 0., 0.) / 255.; + if (id < 1.) { + col = vec3(156., 207., 216.) / 255.; + } else if (id < 2.) { + col = vec3(246., 193., 119.) / 255.; + } else if (id < 3.) { + col = vec3(196., 167., 231.) / 255.; + } else if (id < 4.) { + col = vec3(235., 111., 146.) / 255.; + } + // vec3 col = vec3(id, id, id); + + // float val1 = rand(uv); + // float val2 = rand(val1 + uv); + // float val3 = rand(val2 + uv); + // vec3 col = vec3(val1, val2, val3); + gl_FragColor = vec4(col,1.0); +} From e974170ff04f92bf803b55049b04f88d446d5b86 Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Tue, 3 Feb 2026 10:09:47 +1000 Subject: [PATCH 11/11] add CREDITS.md --- CREDITS.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 CREDITS.md diff --git a/CREDITS.md b/CREDITS.md new file mode 100644 index 0000000..4896e1d --- /dev/null +++ b/CREDITS.md @@ -0,0 +1 @@ +1. [github:rose-pine/tailwind-css](https://github.com/rose-pine/tailwind-css): I modified their css palettes