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
12 changes: 12 additions & 0 deletions src/strands/p5.strands.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ function strands(p5, fn) {
ctx.previousFES = p5.disableFriendlyErrors;
ctx.windowOverrides = {};
ctx.fnOverrides = {};
ctx.graphicsOverrides = {};
if (active) {
p5.disableFriendlyErrors = true;
}
Expand All @@ -71,6 +72,17 @@ function strands(p5, fn) {
for (const key in ctx.fnOverrides) {
fn[key] = ctx.fnOverrides[key];
}
// Clean up the hooks temporarily installed on p5.Graphics.prototype (#8549)
const GraphicsProto = p5.Graphics?.prototype;
if (GraphicsProto) {
for (const key in ctx.graphicsOverrides) {
if (ctx.graphicsOverrides[key] === undefined) {
delete GraphicsProto[key];
} else {
GraphicsProto[key] = ctx.graphicsOverrides[key];
}
}
}
}

const strandsContext = {};
Expand Down
101 changes: 67 additions & 34 deletions src/strands/strands_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,39 @@ function installBuiltinGlobalAccessors(strandsContext) {
strandsContext._builtinGlobalsAccessorsInstalled = true
}

//////////////////////////////////////////////
// Prototype mirroring helpers
//////////////////////////////////////////////

/*
* Permanently augment both p5.prototype (fn) and p5.Graphics.prototype
* with a strands function. Overwrites unconditionally - strands wrappers
* are the correct dual mode implementation.
*/
function augmentFn(fn, p5, name, value) {
fn[name] = value;
const GraphicsProto = p5?.Graphics?.prototype;
if (GraphicsProto) {
GraphicsProto[name] = value;
}
}

/*
* Temporarily augment both p5.prototype (fn) and p5.Graphics.prototype
* with a hook function. Saves the previous own property value in
* graphicsOverrides so deinitStrandsContext can restore it.
*/
function augmentFnTemporary(fn, strandsContext, name, value) {
fn[name] = value;
const GraphicsProto = strandsContext.p5?.Graphics?.prototype;
if (GraphicsProto) {
strandsContext.graphicsOverrides[name] = Object.prototype.hasOwnProperty.call(GraphicsProto, name)
? GraphicsProto[name]
: undefined;
GraphicsProto[name] = value;
}
}

//////////////////////////////////////////////
// User nodes
//////////////////////////////////////////////
Expand All @@ -137,27 +170,27 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
//////////////////////////////////////////////
// Unique Functions
//////////////////////////////////////////////
fn.discard = function() {
augmentFn(fn, p5, 'discard', function() {
build.statementNode(strandsContext, StatementType.DISCARD);
}
fn.break = function() {
});
augmentFn(fn, p5, 'break', function() {
build.statementNode(strandsContext, StatementType.BREAK);
};
});
p5.break = fn.break;
fn.instanceID = function() {
augmentFn(fn, p5, 'instanceID', function() {
const node = build.variableNode(strandsContext, { baseType: BaseType.INT, dimension: 1 }, strandsContext.backend.instanceIdReference());
return createStrandsNode(node.id, node.dimension, strandsContext);
}
});
// Internal methods use p5 static methods; user-facing methods use fn.
// Some methods need to be used by both.
p5.strandsIf = function(conditionNode, ifBody) {
return new StrandsConditional(strandsContext, conditionNode, ifBody);
}
fn.strandsIf = p5.strandsIf;
augmentFn(fn, p5, 'strandsIf', p5.strandsIf);
p5.strandsFor = function(initialCb, conditionCb, updateCb, bodyCb, initialVars) {
return new StrandsFor(strandsContext, initialCb, conditionCb, updateCb, bodyCb, initialVars).build();
};
fn.strandsFor = p5.strandsFor;
augmentFn(fn, p5, 'strandsFor', p5.strandsFor);
p5.strandsEarlyReturn = function(value) {
const { dag, cfg } = strandsContext;

Expand Down Expand Up @@ -190,7 +223,7 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {

return valueNode;
};
fn.strandsEarlyReturn = p5.strandsEarlyReturn;
augmentFn(fn, p5, 'strandsEarlyReturn', p5.strandsEarlyReturn);
p5.strandsNode = function(...args) {
if (args.length === 1 && args[0] instanceof StrandsNode) {
return args[0];
Expand Down Expand Up @@ -221,16 +254,16 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
const isp5Function = overrides[0].isp5Function;
if (isp5Function) {
const originalFn = fn[functionName];
fn[functionName] = function(...args) {
augmentFn(fn, p5, functionName, function(...args) {
if (strandsContext.active) {
const { id, dimension } = build.functionCallNode(strandsContext, functionName, args);
return createStrandsNode(id, dimension, strandsContext);
} else {
return originalFn.apply(this, args);
}
}
});
} else {
fn[functionName] = function (...args) {
augmentFn(fn, p5, functionName, function (...args) {
if (strandsContext.active) {
const { id, dimension } = build.functionCallNode(strandsContext, functionName, args);
return createStrandsNode(id, dimension, strandsContext);
Expand All @@ -239,11 +272,11 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
`It looks like you've called ${functionName} outside of a shader's modify() function.`
)
}
}
});
}
}

fn.getTexture = function (...rawArgs) {
augmentFn(fn, p5, 'getTexture', function (...rawArgs) {
if (strandsContext.active) {
const { id, dimension } = strandsContext.backend.createGetTextureCall(strandsContext, rawArgs);
return createStrandsNode(id, dimension, strandsContext);
Expand All @@ -252,17 +285,17 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
`It looks like you've called getTexture outside of a shader's modify() function.`
)
}
}
});

// Add texture function as alias for getTexture with p5 fallback
const originalTexture = fn.texture;
fn.texture = function (...args) {
augmentFn(fn, p5, 'texture', function (...args) {
if (strandsContext.active) {
return this.getTexture(...args);
} else {
return originalTexture.apply(this, args);
}
}
});

// Add noise function with backend-agnostic implementation
const originalNoise = fn.noise;
Expand All @@ -272,16 +305,16 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
strandsContext._noiseOctaves = null;
strandsContext._noiseAmpFalloff = null;

fn.noiseDetail = function (lod, falloff = 0.5) {
augmentFn(fn, p5, 'noiseDetail', function (lod, falloff = 0.5) {
if (!strandsContext.active) {
return originalNoiseDetail.apply(this, arguments);
}

strandsContext._noiseOctaves = lod;
strandsContext._noiseAmpFalloff = falloff;
};
});

fn.noise = function (...args) {
augmentFn(fn, p5, 'noise', function (...args) {
if (!strandsContext.active) {
return originalNoise.apply(this, args); // fallback to regular p5.js noise
}
Expand Down Expand Up @@ -328,9 +361,9 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
}]
});
return createStrandsNode(id, dimension, strandsContext);
};
});

fn.millis = function (...args) {
augmentFn(fn, p5, 'millis', function (...args) {
if (!strandsContext.active) {
return originalMillis.apply(this, args);
}
Expand All @@ -343,7 +376,7 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
return instance ? instance.millis() : undefined;
}
);
};
});

// Next is type constructors and uniform functions.
// For some of them, we have aliases so that you can write either a more human-readable
Expand Down Expand Up @@ -372,13 +405,13 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
typeAliases.push(pascalTypeName.replace('Vec', 'Vector'));
}
}
fn[`uniform${pascalTypeName}`] = function(name, defaultValue) {
augmentFn(fn, p5, `uniform${pascalTypeName}`, function(name, defaultValue) {
const { id, dimension } = build.variableNode(strandsContext, typeInfo, name);
strandsContext.uniforms.push({ name, typeInfo, defaultValue });
return createStrandsNode(id, dimension, strandsContext);
};
});
// Shared variables with smart context detection
fn[`shared${pascalTypeName}`] = function(name) {
augmentFn(fn, p5, `shared${pascalTypeName}`, function(name) {
const { id, dimension } = build.variableNode(strandsContext, typeInfo, name);

// Initialize shared variables tracking if not present
Expand All @@ -395,20 +428,20 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
});

return createStrandsNode(id, dimension, strandsContext);
};
});

// Alias varying* as shared* for backward compatibility
fn[`varying${pascalTypeName}`] = fn[`shared${pascalTypeName}`];
augmentFn(fn, p5, `varying${pascalTypeName}`, fn[`shared${pascalTypeName}`]);

for (const typeAlias of typeAliases) {
// For compatibility, also alias uniformVec2 as uniformVector2, what we initially
// documented these as
fn[`uniform${typeAlias}`] = fn[`uniform${pascalTypeName}`];
fn[`varying${typeAlias}`] = fn[`varying${pascalTypeName}`];
fn[`shared${typeAlias}`] = fn[`shared${pascalTypeName}`];
augmentFn(fn, p5, `uniform${typeAlias}`, fn[`uniform${pascalTypeName}`]);
augmentFn(fn, p5, `varying${typeAlias}`, fn[`varying${pascalTypeName}`]);
augmentFn(fn, p5, `shared${typeAlias}`, fn[`shared${pascalTypeName}`]);
}
const originalp5Fn = fn[typeInfo.fnName];
fn[typeInfo.fnName] = function(...args) {
augmentFn(fn, p5, typeInfo.fnName, function(...args) {
if (strandsContext.active) {
if (args.length === 1 && args[0].dimension && args[0].dimension === typeInfo.dimension) {
const { id, dimension } = build.functionCallNode(
Expand Down Expand Up @@ -440,7 +473,7 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
`It looks like you've called ${typeInfo.fnName} outside of a shader's modify() function.`
);
}
}
});
}
}
//////////////////////////////////////////////
Expand Down Expand Up @@ -726,7 +759,7 @@ export function createShaderHooksFunctions(strandsContext, fn, shader) {
strandsContext.windowOverrides[name] = window[name];
strandsContext.fnOverrides[name] = fn[name];
window[name] = hook;
fn[name] = hook;
augmentFnTemporary(fn, strandsContext, name, hook);
}
hook.earlyReturns = [];
}
Expand Down
17 changes: 17 additions & 0 deletions test/unit/visual/cases/webgl.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,23 @@ visualSuite('WebGL', function() {
});
});
}

visualTest('On a createGraphics WEBGL buffer', function(p5, screenshot) {
p5.createCanvas(50, 50, p5.WEBGL);

const g = p5.createGraphics(50, 50, p5.WEBGL);
g.background(255);
g.noStroke();
g.fill('red');
g.circle(0, 0, 30);

g.filter(p5.INVERT);

p5.imageMode(p5.CENTER);
p5.image(g, 0, 0);

screenshot();
});
});

visualSuite('Lights', function() {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"numScreenshots": 1
}
Loading