Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/image/filterRenderer2D.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import filterBaseVert from '../webgl/shaders/filters/base.vert';
import webgl2CompatibilityShader from '../webgl/shaders/webgl2Compatibility.glsl';
import { glslBackend } from '../webgl/strands_glslBackend';
import { getShaderHookTypes } from '../webgl/shaderHookUtils';
import randomGLSL from '../webgl/shaders/functions/randomGLSL.glsl';
import randomVertGLSL from '../webgl/shaders/functions/randomVertGLSL.glsl';
import { makeFilterShader } from '../core/filterShaders';

class FilterRenderer2D {
Expand Down Expand Up @@ -309,6 +311,14 @@ class FilterRenderer2D {
}


getRandomFragmentShaderSnippet() {
return randomGLSL;
}

getRandomVertexShaderSnippet() {
return randomVertGLSL;
}

/**
* Set the current filter operation and parameter. If a customShader is provided,
* that overrides the operation-based shader.
Expand Down
4 changes: 4 additions & 0 deletions src/strands/p5.strands.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ function strands(p5, fn) {
ctx.windowOverrides = {};
ctx.fnOverrides = {};
ctx.graphicsOverrides = {};
ctx._randomCallCount = 0;
ctx._randomSeed = null;
if (active) {
p5.disableFriendlyErrors = true;
}
Expand All @@ -64,6 +66,8 @@ function strands(p5, fn) {
ctx.computeDeclarations = new Set();
ctx.hooks = [];
ctx.active = false;
ctx._randomCallCount = 0;
ctx._randomSeed = null;
p5.disableFriendlyErrors = ctx.previousFES;
for (const key in ctx.windowOverrides) {
window[key] = ctx.windowOverrides[key];
Expand Down
85 changes: 85 additions & 0 deletions src/strands/strands_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,8 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
// Add noise function with backend-agnostic implementation
const originalNoise = fn.noise;
const originalNoiseDetail = fn.noiseDetail;
const originalRandom = fn.random;
const originalRandomSeed = fn.randomSeed;
const originalMillis = fn.millis;

strandsContext._noiseOctaves = null;
Expand Down Expand Up @@ -382,6 +384,89 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
return createStrandsNode(id, dimension, strandsContext);
});

strandsContext._randomSeed = null;
strandsContext._randomCallCount = 0;

augmentFn(fn, p5, 'randomSeed', function (seed) {
if (!strandsContext.active) {
return originalRandomSeed.apply(this, arguments);
}
strandsContext._randomSeed = seed;
});

augmentFn(fn, p5, 'random', function (...args) {
if (!strandsContext.active) {
return originalRandom.apply(this, args);
}

const randomVertSnippet = strandsContext.backend.getRandomVertexShaderSnippet();
const randomFragSnippet = strandsContext.backend.getRandomFragmentShaderSnippet();

strandsContext.vertexDeclarations.add(randomVertSnippet);
strandsContext.fragmentDeclarations.add(randomFragSnippet);

if (strandsContext.backend.getRandomComputeShaderSnippet) {
const randomComputeSnippet = strandsContext.backend.getRandomComputeShaderSnippet();
strandsContext.computeDeclarations.add(randomComputeSnippet);
}

let seedNode;
if (strandsContext._randomSeed !== null && strandsContext._randomSeed.isStrandsNode) {
seedNode = strandsContext._randomSeed;
} else {
const userSeed = strandsContext._randomSeed;
seedNode = getOrCreateUniformNode(
strandsContext,
'_p5_randomSeed',
DataType.float1,
userSeed !== null
? () => userSeed
: () => performance.now(),
);
}

const callIndex = strandsContext._randomCallCount++;

const nodeArgs = [seedNode, callIndex];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that means that if you were to call random() in a for loop, e.g.:

let data = uniformStorage(someStorage)

for (let i = 0; i < 5; i++) {
  data[i] += random()
}

...then they'd all get the same random value added to them because random here is just one AST node and would have one fixed call index, translating into something like:

for (var i = 0; i < 5; i++) {
  data[i] += random(seed, 0);
}

Do we have the option of making the call index be a global variable in the shader code so that it's not fixed? e.g something like:

var<private> invocation: i32 = 0;
fn random(seed: f32) -> f32 {
  let val = ...; // Do random gen here, using `invocation`
  invocation++;
  return val;
}

// So then the call site is just:
for (var i = 0; i < 5; i++) {
  data[i] += random(seed);
}


if (args.length === 0) {
const { id, dimension } = build.functionCallNode(strandsContext, 'random', nodeArgs, {
overloads: [{
params: [DataType.float1, DataType.float1],
returnType: DataType.float1,
}]
});
return createStrandsNode(id, dimension, strandsContext);
} else if (args.length === 1) {
// random(max) → [0, max)
const rawNode = build.functionCallNode(strandsContext, 'random', nodeArgs, {
overloads: [{
params: [DataType.float1, DataType.float1],
returnType: DataType.float1,
}]
});
const rawStrandsNode = createStrandsNode(rawNode.id, rawNode.dimension, strandsContext);
return rawStrandsNode.mult(p5.strandsNode(args[0]));
} else if (args.length === 2) {
// random(min, max) → [min, max)
const rawNode = build.functionCallNode(strandsContext, 'random', nodeArgs, {
overloads: [{
params: [DataType.float1, DataType.float1],
returnType: DataType.float1,
}]
});
const rawStrandsNode = createStrandsNode(rawNode.id, rawNode.dimension, strandsContext);
const minNode = p5.strandsNode(args[0]);
const maxNode = p5.strandsNode(args[1]);
// min + raw * (max - min)
return rawStrandsNode.mult(maxNode.sub(minNode)).add(minNode);
} else {
p5._friendlyError(
`It looks like you've called random() with ${args.length} arguments. In strands, random() supports 0, 1, or 2 numeric arguments.`
);
}
});

augmentFn(fn, p5, 'millis', function (...args) {
if (!strandsContext.active) {
return originalMillis.apply(this, args);
Expand Down
23 changes: 23 additions & 0 deletions src/webgl/shaders/functions/randomGLSL.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// _p5_hash: "Hash without Sine" by Dave Hoskins (https://www.shadertoy.com/view/4djSRW)
// Mixing constants: R₂ sequence by Martin Roberts (https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/)
// α₁ = 1/φ₂ = 0.7548776662 (plastic constant reciprocal)
// α₂ = 1/φ₂² = 0.5698402910
// 1/φ = 0.6180339887 (golden ratio conjugate)

float _p5_hash(vec3 p) {
p = fract(p * vec3(0.1031, 0.1030, 0.0973));
p += dot(p, p.yxz + 33.33);
return fract((p.x + p.y) * p.z);
}

float random(float seed, float callIndex) {
vec2 pixelCoord = gl_FragCoord.xy;
// fract(seed * α₁) normalizes large seeds (e.g. performance.now()) into [0,1)
// and spreads them optimally via the R₂ sequence's plastic constant
float s = fract(seed * 0.7548776662);
return _p5_hash(vec3(
pixelCoord.x + s,
pixelCoord.y + callIndex * 0.5698402910,
s + callIndex * 0.6180339887
));
}
21 changes: 21 additions & 0 deletions src/webgl/shaders/functions/randomVertGLSL.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// _p5_hash: "Hash without Sine" by Dave Hoskins (https://www.shadertoy.com/view/4djSRW)
// Mixing constants: R₂ sequence by Martin Roberts (https://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/)
// α₁ = 1/φ₂ = 0.7548776662 (plastic constant reciprocal)
// α₂ = 1/φ₂² = 0.5698402910
// 1/φ = 0.6180339887 (golden ratio conjugate)

float _p5_hash(vec3 p) {
p = fract(p * vec3(0.1031, 0.1030, 0.0973));
p += dot(p, p.yxz + 33.33);
return fract((p.x + p.y) * p.z);
}

float random(float seed, float callIndex) {
float vid = float(gl_VertexID);
float s = fract(seed * 0.7548776662);
return _p5_hash(vec3(
vid + s,
vid * 0.5698402910 + callIndex * 0.6180339887,
s + callIndex * 0.7548776662
));
}
8 changes: 8 additions & 0 deletions src/webgl/strands_glslBackend.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import noiseGLSL from './shaders/functions/noise3DGLSL.glsl';
import randomGLSL from './shaders/functions/randomGLSL.glsl';
import randomVertGLSL from './shaders/functions/randomVertGLSL.glsl';
import { NodeType, OpCodeToSymbol, BlockType, OpCode, NodeTypeToName, isStructType, BaseType, StatementType, DataType, INSTANCE_ID_VARYING_NAME } from "../strands/ir_types";
import { getNodeDataFromID, extractNodeTypeInfo } from "../strands/ir_dag";
import * as FES from '../strands/strands_FES';
Expand Down Expand Up @@ -173,6 +175,12 @@ export const glslBackend = {
getNoiseShaderSnippet() {
return noiseGLSL;
},
getRandomFragmentShaderSnippet() {
return randomGLSL;
},
getRandomVertexShaderSnippet() {
return randomVertGLSL;
},
getTypeName(baseType, dimension) {
const primitiveTypeName = TypeNames[baseType + dimension]
if (!primitiveTypeName) {
Expand Down
Loading
Loading