Skip to content
This repository was archived by the owner on Jun 19, 2025. It is now read-only.

Commit 5c983ce

Browse files
Extract the uniform helper to its own module
1 parent 735d531 commit 5c983ce

File tree

3 files changed

+246
-234
lines changed

3 files changed

+246
-234
lines changed

packages/shader/index.mjs

Lines changed: 2 additions & 234 deletions
Original file line numberDiff line numberDiff line change
@@ -1,234 +1,2 @@
1-
/*
2-
shader.mjs - implements the `loadShader` helper and `shader` pattern function
3-
Copyright (C) 2024 Strudel contributors
4-
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
5-
*/
6-
7-
import { PicoGL } from 'picogl';
8-
import { register, logger } from '@strudel/core';
9-
10-
// The standard fullscreen vertex shader.
11-
const vertexShader = `#version 300 es
12-
precision highp float;
13-
layout(location=0) in vec2 position;
14-
void main() {
15-
gl_Position = vec4(position, 1, 1);
16-
}
17-
`;
18-
19-
// Make the fragment source, similar to the one from shadertoy.
20-
function mkFragmentShader(code) {
21-
return `#version 300 es
22-
precision highp float;
23-
out vec4 oColor;
24-
uniform float iTime;
25-
uniform vec2 iResolution;
26-
27-
${code}
28-
29-
void main(void) {
30-
mainImage(oColor, gl_FragCoord.xy);
31-
}
32-
`;
33-
}
34-
35-
// Modulation helpers.
36-
const hardModulation = () => {
37-
let val = 0;
38-
return {
39-
get: () => val,
40-
set: (v) => {
41-
val = v;
42-
},
43-
};
44-
};
45-
46-
const decayModulation = (decay) => {
47-
let val = 0;
48-
let desired = 0;
49-
return {
50-
get: (ts) => {
51-
val += (desired - val) / decay;
52-
return val;
53-
},
54-
set: (v) => {
55-
desired = val + v;
56-
},
57-
};
58-
};
59-
60-
// Set an uniform value (from a pattern).
61-
function setUniform(instance, name, value, position) {
62-
const uniform = instance.uniforms[name];
63-
if (uniform) {
64-
if (uniform.count == 0) {
65-
// This is a single value
66-
uniform.mod.set(value);
67-
} else {
68-
// This is an array
69-
const idx = position % uniform.mod.length;
70-
uniform.mod[idx].set(value);
71-
}
72-
} else {
73-
logger('[shader] unknown uniform: ' + name);
74-
}
75-
76-
// Ensure the instance is drawn
77-
instance.age = 0;
78-
if (!instance.drawing) {
79-
instance.drawing = requestAnimationFrame(instance.update);
80-
}
81-
}
82-
83-
// Update the uniforms for a given drawFrame call.
84-
function updateUniforms(drawFrame, elapsed, uniforms) {
85-
Object.values(uniforms).forEach((uniform) => {
86-
const value =
87-
uniform.count == 0 ? uniform.mod.get(elapsed) : uniform.value.map((_, i) => uniform.mod[i].get(elapsed));
88-
// Send the value to the GPU
89-
drawFrame.uniform(uniform.name, value);
90-
});
91-
}
92-
93-
// Setup the instance's uniform after shader compilation.
94-
function setupUniforms(uniforms, program) {
95-
Object.entries(program.uniforms).forEach(([name, uniform]) => {
96-
if (name != 'iTime' && name != 'iResolution') {
97-
// remove array suffix
98-
const uname = name.replace('[0]', '');
99-
const count = uniform.count | 0;
100-
if (!uniforms[uname] || uniforms[uname].count != count) {
101-
// TODO: keep the previous value when the count change...
102-
uniforms[uname] = {
103-
name,
104-
count,
105-
value: count == 0 ? 0 : new Float32Array(count),
106-
mod: count == 0 ? decayModulation(50) : new Array(count).fill().map(() => decayModulation(50)),
107-
};
108-
}
109-
}
110-
});
111-
// TODO: remove previous uniform that are no longer used...
112-
return uniforms;
113-
}
114-
115-
// Setup the canvas and return the WebGL context.
116-
function setupCanvas(name) {
117-
// TODO: support custom size
118-
const width = 400;
119-
const height = 300;
120-
const canvas = document.createElement('canvas');
121-
canvas.id = 'cnv-' + name;
122-
canvas.width = width;
123-
canvas.height = height;
124-
const top = 60 + Object.keys(_instances).length * height;
125-
canvas.style = `pointer-events:none;width:${width}px;height:${height}px;position:fixed;top:${top}px;right:23px`;
126-
document.body.append(canvas);
127-
return canvas.getContext('webgl2');
128-
}
129-
130-
// Setup the shader instance
131-
async function initializeShaderInstance(name, code) {
132-
// Setup PicoGL app
133-
const ctx = setupCanvas(name);
134-
console.log(ctx);
135-
const app = PicoGL.createApp(ctx);
136-
app.resize(400, 300);
137-
138-
// Setup buffers
139-
const resolution = new Float32Array([ctx.canvas.width, ctx.canvas.height]);
140-
141-
// Two triangle to cover the whole canvas
142-
const positionBuffer = app.createVertexBuffer(
143-
PicoGL.FLOAT,
144-
2,
145-
new Float32Array([-1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1]),
146-
);
147-
148-
// Setup the arrays
149-
const arrays = app.createVertexArray().vertexAttributeBuffer(0, positionBuffer);
150-
151-
return app
152-
.createPrograms([vertexShader, code])
153-
.then(([program]) => {
154-
const drawFrame = app.createDrawCall(program, arrays);
155-
const instance = { app, code, program, arrays, drawFrame, uniforms: setupUniforms({}, program) };
156-
157-
// Render frame logic
158-
let prev = performance.now() / 1000;
159-
instance.age = 0;
160-
instance.update = () => {
161-
const now = performance.now() / 1000;
162-
const elapsed = now - prev;
163-
prev = now;
164-
// console.log("drawing!")
165-
app.clear();
166-
instance.drawFrame.uniform('iResolution', resolution).uniform('iTime', now);
167-
168-
updateUniforms(instance.drawFrame, elapsed, instance.uniforms);
169-
170-
instance.drawFrame.draw();
171-
if (instance.age++ < 100) requestAnimationFrame(instance.update);
172-
else instance.drawing = false;
173-
};
174-
return instance;
175-
})
176-
.catch((err) => {
177-
ctx.canvas.remove();
178-
throw err;
179-
});
180-
}
181-
182-
// Update the instance program
183-
async function reloadShaderInstanceCode(instance, code) {
184-
return instance.app.createPrograms([vertexShader, code]).then(([program]) => {
185-
instance.program.delete();
186-
instance.program = program;
187-
instance.uniforms = setupUniforms(instance.uniforms, program);
188-
instance.drawFrame = instance.app.createDrawCall(program, instance.arrays);
189-
});
190-
}
191-
192-
// Keep track of the running shader instances
193-
let _instances = {};
194-
export async function loadShader(code = '', name = 'default') {
195-
if (code) {
196-
code = mkFragmentShader(code);
197-
}
198-
if (!_instances[name]) {
199-
_instances[name] = await initializeShaderInstance(name, code);
200-
logger('[shader] ready');
201-
} else if (_instances[name].code != code) {
202-
await reloadShaderInstanceCode(_instances[name], code);
203-
logger('[shader] reloaded');
204-
}
205-
}
206-
207-
export const shader = register('shader', (options, pat) => {
208-
// Keep track of the pitches value: Map String Int
209-
const pitches = { _count: 0 };
210-
211-
return pat.onTrigger((time_deprecate, hap, currentTime, cps, targetTime) => {
212-
const instance = _instances[options.instance || 'default'];
213-
if (!instance) {
214-
logger('[shader] not loaded yet', 'warning');
215-
return;
216-
}
217-
218-
const value = options.gain || 1.0;
219-
if (options.pitch !== undefined) {
220-
const note = hap.value.note || hap.value.s;
221-
if (pitches[note] === undefined) {
222-
// Assign new value, the first note gets 0, then 1, then 2, ...
223-
pitches[note] = Object.keys(pitches).length;
224-
}
225-
setUniform(instance, options.pitch, value, pitches[note]);
226-
} else if (options.seq !== undefined) {
227-
setUniform(instance, options.seq, value, pitches._count++);
228-
} else if (options.uniform !== undefined) {
229-
setUniform(instance, options.uniform, value);
230-
} else {
231-
console.error('Unknown shader options, need either pitch or uniform', options);
232-
}
233-
}, false);
234-
});
1+
export {loadShader} from './shader.mjs';
2+
export * from './uniform.mjs';

0 commit comments

Comments
 (0)