Skip to content

Commit

Permalink
Remove uniform pattern function and expose the shader instance
Browse files Browse the repository at this point in the history
  • Loading branch information
TristanCacqueray committed Oct 27, 2024
1 parent 65cc317 commit 77bbb96
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 223 deletions.
1 change: 0 additions & 1 deletion packages/shader/index.mjs
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from './shader.mjs';
export * from './uniform.mjs';
146 changes: 82 additions & 64 deletions packages/shader/shader.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,88 +34,105 @@ void main(void) {
`;
}

// Modulation helpers to smooth the values.
// Helper class to handle uniform updates
class UniformValue {
constructor() {
this.value = 0;
this.desired = 0;
this.slow = 10;
constructor(name, count, draw) {
this.name = name;
this.draw = draw;
this.isArray = count > 0;
this.value = new Array(Math.max(1, count)).fill(0);
this.frameModifier = new Array(Math.max(1, count)).fill(null);
}

get(elapsed) {
// Adjust the value according to the rate of change
const offset = (this.desired - this.value) / (this.slow * Math.max(1, elapsed * 60));
// Ignore small changes
if (Math.abs(offset) > 1e-3) this.value += offset;
return this.value;
}
}

// Set an uniform value (from a pattern).
export function setUniform(instanceName, name, value, incr, position, slow) {
const instance = _instances[instanceName];
if (!instance) {
logger('[shader] not loaded yet', 'warning');
return;
incr(value, pos = 0) {
const idx = pos % this.value.length;
this.value[idx] += value;
this.frameModifier[idx] = null;
this.draw();
}

// console.log('setUniform: ', name, value, position, slow);
const uniform = instance.uniforms[name];
if (uniform) {
let uniformValue;
if (uniform.count == 0) {
// This is a single value
uniformValue = uniform.value;
set(value, pos = 0) {
const idx = pos % this.value.length;
if (typeof value === 'function') {
this.frameModifier[idx] = value;
} else {
// This is an array
const idx = position % uniform.value.length;
uniformValue = uniform.value[idx];
this.value[idx] = value;
this.frameModifier[idx] = null;
}
uniformValue.slow = slow;
if (incr) uniformValue.desired += value;
else uniformValue.desired = value;
} else {
logger('[shader] unknown uniform: ' + name);
this.draw();
}

get(pos = 0) {
return this.value[pos % this.value.length];
}

_frameUpdate(elapsed) {
this.value = this.value.map((value, idx) =>
this.frameModifier[idx] ? this.frameModifier[idx](value, elapsed) : value,
);
return this.isArray ? this.value : this.value[0];
}

// Ensure the instance is drawn
instance.age = 0;
if (!instance.drawing) {
instance.drawing = requestAnimationFrame(instance.update);
_resize(count) {
if (count != this.count) {
this.isArray = count > 0;
count = Math.max(1, count);
resizeArray(this.value, count, 0);
resizeArray(this.frameModifier, count, null);
}
}
}

// Update the uniforms for a given drawFrame call.
function updateUniforms(drawFrame, elapsed, uniforms) {
Object.values(uniforms).forEach((uniform) => {
const value = uniform.count == 0 ? uniform.value.get(elapsed) : uniform.value.map((v) => v.get(elapsed));
// Shrink or extend an array
function resizeArray(arr, size, defval) {
if (arr.length > size) arr.length = size;
else arr.push(...new Array(size - arr.length).fill(defval));
}

// Send the value to the GPU
// console.log('updateUniforms:', uniform.name, value);
drawFrame.uniform(uniform.name, value);
});
// Get the size of an uniform
function uniformSize(funcName) {
if (funcName == 'uniform3fv') return 3;
else if (funcName == 'uniform4fv') return 4;
return 1;
}

// Setup the instance's uniform after shader compilation.
function setupUniforms(uniforms, program) {
Object.entries(program.uniforms).forEach(([name, uniform]) => {
function setupUniforms(instance, resetDraw = false) {
const newUniforms = new Set();
const draw = () => {
// Start the drawing loop
instance.age = 0;
if (!instance.drawing) {
instance.drawing = requestAnimationFrame(instance.update);
}
};
Object.entries(instance.program.uniforms).forEach(([name, uniform]) => {
if (name != 'iTime' && name != 'iResolution') {
// remove array suffix
const uname = name.replace('[0]', '');
const count = uniform.count | 0;
if (!uniforms[uname] || uniforms[uname].count != count) {
// TODO: keep the previous values when the count change:
// if the count decreased, then drop the excess, else append new values
uniforms[uname] = {
name,
count,
value: count == 0 ? new UniformValue() : new Array(count).fill().map(() => new UniformValue()),
};
}
newUniforms.add(uname);
const count = (uniform.count | 0) * uniformSize(uniform.glFuncName);
if (!instance.uniforms[uname]) instance.uniforms[uname] = new UniformValue(name, count, draw);
else instance.uniforms[uname]._resize(count);
if (resetDraw) instance.uniforms[uname].draw = draw;
}
});
// TODO: remove previous uniform that are no longer used...
return uniforms;

// Remove deleted uniforms
Object.keys(instance.uniforms).forEach((name) => {
if (!newUniforms.has(name)) delete instance.uniforms[name];
});
}

// Update the uniforms for a given drawFrame call.
function updateUniforms(drawFrame, elapsed, uniforms) {
Object.values(uniforms).forEach((uniform) => {
const value = uniform._frameUpdate(elapsed);

// Send the value to the GPU
// console.log('updateUniforms:', uniform.name, value);
drawFrame.uniform(uniform.name, value);
});
}

// Setup the canvas and return the WebGL context.
Expand Down Expand Up @@ -156,8 +173,8 @@ async function initializeShaderInstance(name, code) {
.createPrograms([vertexShader, code])
.then(([program]) => {
const drawFrame = app.createDrawCall(program, arrays);
const instance = { app, code, program, arrays, drawFrame, uniforms: setupUniforms({}, program) };

const instance = { app, code, program, arrays, drawFrame, uniforms: {} };
setupUniforms(instance);
// Render frame logic
let prev = performance.now() / 1000;
instance.age = 0;
Expand Down Expand Up @@ -189,8 +206,8 @@ async function reloadShaderInstanceCode(instance, code) {
return instance.app.createPrograms([vertexShader, code]).then(([program]) => {
instance.program.delete();
instance.program = program;
instance.uniforms = setupUniforms(instance.uniforms, program);
instance.drawFrame = instance.app.createDrawCall(program, instance.arrays);
setupUniforms(instance, true);
});
}

Expand All @@ -207,4 +224,5 @@ export async function loadShader(code = '', name = 'default') {
await reloadShaderInstanceCode(_instances[name], code);
logger('[shader] reloaded');
}
return _instances[name];
}
125 changes: 0 additions & 125 deletions packages/shader/uniform.mjs

This file was deleted.

30 changes: 0 additions & 30 deletions test/__snapshots__/examples.test.mjs.snap
Original file line number Diff line number Diff line change
Expand Up @@ -8343,36 +8343,6 @@ exports[`runs examples > example "undegradeBy" example index 1 1`] = `
]
`;

exports[`runs examples > example "uniform" example index 0 1`] = `
[
"[ ~show() {
return this.begin.show() + '' + this.end.show();
} | pan:0.4999999999999998 ]",
]
`;

exports[`runs examples > example "uniform" example index 1 1`] = `
[
"[ 0/1 → 1/1 | gain:0.5 ]",
"[ 1/1 → 2/1 | gain:0.3 ]",
"[ 2/1 → 3/1 | gain:0.5 ]",
"[ 3/1 → 4/1 | gain:0.3 ]",
]
`;

exports[`runs examples > example "uniform" example index 2 1`] = `
[
"[ 0/1 → 1/2 | s:bd ]",
"[ 1/2 → 1/1 | s:sd ]",
"[ 1/1 → 3/2 | s:bd ]",
"[ 3/2 → 2/1 | s:sd ]",
"[ 2/1 → 5/2 | s:bd ]",
"[ 5/2 → 3/1 | s:sd ]",
"[ 3/1 → 7/2 | s:bd ]",
"[ 7/2 → 4/1 | s:sd ]",
]
`;

exports[`runs examples > example "unison" example index 0 1`] = `
[
"[ 0/1 → 1/12 | note:d s:supersaw unison:1 ]",
Expand Down
3 changes: 0 additions & 3 deletions test/runtime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,6 @@ const toneHelpersMocked = {
strudel.Pattern.prototype.osc = function () {
return this;
};
strudel.Pattern.prototype.uniform = function () {
return this;
};
strudel.Pattern.prototype.csound = function () {
return this;
};
Expand Down

0 comments on commit 77bbb96

Please sign in to comment.