Skip to content

Commit f4ce8a0

Browse files
authored
Merge pull request #8568 from aashu2006/fix-graphics-filter-strands
Fix filter() crash on createGraphics(WEBGL) by mirroring strands API …
2 parents dbf8367 + 3c785bb commit f4ce8a0

File tree

5 files changed

+102
-37
lines changed

5 files changed

+102
-37
lines changed

src/strands/p5.strands.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ function strands(p5, fn) {
4949
ctx.previousFES = p5.disableFriendlyErrors;
5050
ctx.windowOverrides = {};
5151
ctx.fnOverrides = {};
52+
ctx.graphicsOverrides = {};
5253
if (active) {
5354
p5.disableFriendlyErrors = true;
5455
}
@@ -71,6 +72,17 @@ function strands(p5, fn) {
7172
for (const key in ctx.fnOverrides) {
7273
fn[key] = ctx.fnOverrides[key];
7374
}
75+
// Clean up the hooks temporarily installed on p5.Graphics.prototype (#8549)
76+
const GraphicsProto = p5.Graphics?.prototype;
77+
if (GraphicsProto) {
78+
for (const key in ctx.graphicsOverrides) {
79+
if (ctx.graphicsOverrides[key] === undefined) {
80+
delete GraphicsProto[key];
81+
} else {
82+
GraphicsProto[key] = ctx.graphicsOverrides[key];
83+
}
84+
}
85+
}
7486
}
7587

7688
const strandsContext = {};

src/strands/strands_api.js

Lines changed: 70 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,42 @@ function installBuiltinGlobalAccessors(strandsContext) {
114114
strandsContext._builtinGlobalsAccessorsInstalled = true
115115
}
116116

117+
//////////////////////////////////////////////
118+
// Prototype mirroring helpers
119+
//////////////////////////////////////////////
120+
121+
/*
122+
* Permanently augment both p5.prototype (fn) and p5.Graphics.prototype
123+
* with a strands function. Overwrites unconditionally - strands wrappers
124+
* are the correct dual mode implementation.
125+
*/
126+
function augmentFn(fn, p5, name, value) {
127+
fn[name] = value;
128+
const GraphicsProto = p5?.Graphics?.prototype;
129+
if (GraphicsProto) {
130+
GraphicsProto[name] = value;
131+
}
132+
}
133+
134+
/*
135+
* Temporarily augment window, p5.prototype (fn), and p5.Graphics.prototype
136+
* with a hook function. Saves previous values into strandsContext override
137+
* stores so deinitStrandsContext can restore them.
138+
*/
139+
function augmentFnTemporary(fn, strandsContext, name, value) {
140+
strandsContext.windowOverrides[name] = window[name];
141+
strandsContext.fnOverrides[name] = fn[name];
142+
window[name] = value;
143+
fn[name] = value;
144+
const GraphicsProto = strandsContext.p5?.Graphics?.prototype;
145+
if (GraphicsProto) {
146+
strandsContext.graphicsOverrides[name] = Object.prototype.hasOwnProperty.call(GraphicsProto, name)
147+
? GraphicsProto[name]
148+
: undefined;
149+
GraphicsProto[name] = value;
150+
}
151+
}
152+
117153
//////////////////////////////////////////////
118154
// User nodes
119155
//////////////////////////////////////////////
@@ -137,27 +173,27 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
137173
//////////////////////////////////////////////
138174
// Unique Functions
139175
//////////////////////////////////////////////
140-
fn.discard = function() {
176+
augmentFn(fn, p5, 'discard', function() {
141177
build.statementNode(strandsContext, StatementType.DISCARD);
142-
}
143-
fn.break = function() {
178+
});
179+
augmentFn(fn, p5, 'break', function() {
144180
build.statementNode(strandsContext, StatementType.BREAK);
145-
};
181+
});
146182
p5.break = fn.break;
147-
fn.instanceID = function() {
183+
augmentFn(fn, p5, 'instanceID', function() {
148184
const node = build.variableNode(strandsContext, { baseType: BaseType.INT, dimension: 1 }, strandsContext.backend.instanceIdReference());
149185
return createStrandsNode(node.id, node.dimension, strandsContext);
150-
}
186+
});
151187
// Internal methods use p5 static methods; user-facing methods use fn.
152188
// Some methods need to be used by both.
153189
p5.strandsIf = function(conditionNode, ifBody) {
154190
return new StrandsConditional(strandsContext, conditionNode, ifBody);
155191
}
156-
fn.strandsIf = p5.strandsIf;
192+
augmentFn(fn, p5, 'strandsIf', p5.strandsIf);
157193
p5.strandsFor = function(initialCb, conditionCb, updateCb, bodyCb, initialVars) {
158194
return new StrandsFor(strandsContext, initialCb, conditionCb, updateCb, bodyCb, initialVars).build();
159195
};
160-
fn.strandsFor = p5.strandsFor;
196+
augmentFn(fn, p5, 'strandsFor', p5.strandsFor);
161197
p5.strandsEarlyReturn = function(value) {
162198
const { dag, cfg } = strandsContext;
163199

@@ -190,7 +226,7 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
190226

191227
return valueNode;
192228
};
193-
fn.strandsEarlyReturn = p5.strandsEarlyReturn;
229+
augmentFn(fn, p5, 'strandsEarlyReturn', p5.strandsEarlyReturn);
194230
p5.strandsNode = function(...args) {
195231
if (args.length === 1 && args[0] instanceof StrandsNode) {
196232
return args[0];
@@ -221,16 +257,16 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
221257
const isp5Function = overrides[0].isp5Function;
222258
if (isp5Function) {
223259
const originalFn = fn[functionName];
224-
fn[functionName] = function(...args) {
260+
augmentFn(fn, p5, functionName, function(...args) {
225261
if (strandsContext.active) {
226262
const { id, dimension } = build.functionCallNode(strandsContext, functionName, args);
227263
return createStrandsNode(id, dimension, strandsContext);
228264
} else {
229265
return originalFn.apply(this, args);
230266
}
231-
}
267+
});
232268
} else {
233-
fn[functionName] = function (...args) {
269+
augmentFn(fn, p5, functionName, function (...args) {
234270
if (strandsContext.active) {
235271
const { id, dimension } = build.functionCallNode(strandsContext, functionName, args);
236272
return createStrandsNode(id, dimension, strandsContext);
@@ -239,11 +275,11 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
239275
`It looks like you've called ${functionName} outside of a shader's modify() function.`
240276
)
241277
}
242-
}
278+
});
243279
}
244280
}
245281

246-
fn.getTexture = function (...rawArgs) {
282+
augmentFn(fn, p5, 'getTexture', function (...rawArgs) {
247283
if (strandsContext.active) {
248284
const { id, dimension } = strandsContext.backend.createGetTextureCall(strandsContext, rawArgs);
249285
return createStrandsNode(id, dimension, strandsContext);
@@ -252,17 +288,17 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
252288
`It looks like you've called getTexture outside of a shader's modify() function.`
253289
)
254290
}
255-
}
291+
});
256292

257293
// Add texture function as alias for getTexture with p5 fallback
258294
const originalTexture = fn.texture;
259-
fn.texture = function (...args) {
295+
augmentFn(fn, p5, 'texture', function (...args) {
260296
if (strandsContext.active) {
261297
return this.getTexture(...args);
262298
} else {
263299
return originalTexture.apply(this, args);
264300
}
265-
}
301+
});
266302

267303
// Add noise function with backend-agnostic implementation
268304
const originalNoise = fn.noise;
@@ -272,16 +308,16 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
272308
strandsContext._noiseOctaves = null;
273309
strandsContext._noiseAmpFalloff = null;
274310

275-
fn.noiseDetail = function (lod, falloff = 0.5) {
311+
augmentFn(fn, p5, 'noiseDetail', function (lod, falloff = 0.5) {
276312
if (!strandsContext.active) {
277313
return originalNoiseDetail.apply(this, arguments);
278314
}
279315

280316
strandsContext._noiseOctaves = lod;
281317
strandsContext._noiseAmpFalloff = falloff;
282-
};
318+
});
283319

284-
fn.noise = function (...args) {
320+
augmentFn(fn, p5, 'noise', function (...args) {
285321
if (!strandsContext.active) {
286322
return originalNoise.apply(this, args); // fallback to regular p5.js noise
287323
}
@@ -328,9 +364,9 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
328364
}]
329365
});
330366
return createStrandsNode(id, dimension, strandsContext);
331-
};
367+
});
332368

333-
fn.millis = function (...args) {
369+
augmentFn(fn, p5, 'millis', function (...args) {
334370
if (!strandsContext.active) {
335371
return originalMillis.apply(this, args);
336372
}
@@ -343,7 +379,7 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
343379
return instance ? instance.millis() : undefined;
344380
}
345381
);
346-
};
382+
});
347383

348384
// Next is type constructors and uniform functions.
349385
// For some of them, we have aliases so that you can write either a more human-readable
@@ -372,13 +408,13 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
372408
typeAliases.push(pascalTypeName.replace('Vec', 'Vector'));
373409
}
374410
}
375-
fn[`uniform${pascalTypeName}`] = function(name, defaultValue) {
411+
augmentFn(fn, p5, `uniform${pascalTypeName}`, function(name, defaultValue) {
376412
const { id, dimension } = build.variableNode(strandsContext, typeInfo, name);
377413
strandsContext.uniforms.push({ name, typeInfo, defaultValue });
378414
return createStrandsNode(id, dimension, strandsContext);
379-
};
415+
});
380416
// Shared variables with smart context detection
381-
fn[`shared${pascalTypeName}`] = function(name) {
417+
augmentFn(fn, p5, `shared${pascalTypeName}`, function(name) {
382418
const { id, dimension } = build.variableNode(strandsContext, typeInfo, name);
383419

384420
// Initialize shared variables tracking if not present
@@ -395,20 +431,20 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
395431
});
396432

397433
return createStrandsNode(id, dimension, strandsContext);
398-
};
434+
});
399435

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

403439
for (const typeAlias of typeAliases) {
404440
// For compatibility, also alias uniformVec2 as uniformVector2, what we initially
405441
// documented these as
406-
fn[`uniform${typeAlias}`] = fn[`uniform${pascalTypeName}`];
407-
fn[`varying${typeAlias}`] = fn[`varying${pascalTypeName}`];
408-
fn[`shared${typeAlias}`] = fn[`shared${pascalTypeName}`];
442+
augmentFn(fn, p5, `uniform${typeAlias}`, fn[`uniform${pascalTypeName}`]);
443+
augmentFn(fn, p5, `varying${typeAlias}`, fn[`varying${pascalTypeName}`]);
444+
augmentFn(fn, p5, `shared${typeAlias}`, fn[`shared${pascalTypeName}`]);
409445
}
410446
const originalp5Fn = fn[typeInfo.fnName];
411-
fn[typeInfo.fnName] = function(...args) {
447+
augmentFn(fn, p5, typeInfo.fnName, function(...args) {
412448
if (strandsContext.active) {
413449
if (args.length === 1 && args[0].dimension && args[0].dimension === typeInfo.dimension) {
414450
const { id, dimension } = build.functionCallNode(
@@ -440,7 +476,7 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
440476
`It looks like you've called ${typeInfo.fnName} outside of a shader's modify() function.`
441477
);
442478
}
443-
}
479+
});
444480
}
445481
}
446482
//////////////////////////////////////////////
@@ -723,10 +759,7 @@ export function createShaderHooksFunctions(strandsContext, fn, shader) {
723759
}
724760

725761
for (const name of aliases) {
726-
strandsContext.windowOverrides[name] = window[name];
727-
strandsContext.fnOverrides[name] = fn[name];
728-
window[name] = hook;
729-
fn[name] = hook;
762+
augmentFnTemporary(fn, strandsContext, name, hook);
730763
}
731764
hook.earlyReturns = [];
732765
}

test/unit/visual/cases/webgl.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,23 @@ visualSuite('WebGL', function() {
255255
});
256256
});
257257
}
258+
259+
visualTest('On a createGraphics WEBGL buffer', function(p5, screenshot) {
260+
p5.createCanvas(50, 50, p5.WEBGL);
261+
262+
const g = p5.createGraphics(50, 50, p5.WEBGL);
263+
g.background(255);
264+
g.noStroke();
265+
g.fill('red');
266+
g.circle(0, 0, 30);
267+
268+
g.filter(p5.INVERT);
269+
270+
p5.imageMode(p5.CENTER);
271+
p5.image(g, 0, 0);
272+
273+
screenshot();
274+
});
258275
});
259276

260277
visualSuite('Lights', function() {
577 Bytes
Loading
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"numScreenshots": 1
3+
}

0 commit comments

Comments
 (0)