Skip to content

Commit e862686

Browse files
authored
Merge branch 'dev-2.0' into patch-2
2 parents ee9f8c7 + 514df2a commit e862686

File tree

72 files changed

+145
-58
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+145
-58
lines changed

src/strands/p5.strands.js

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -65,39 +65,41 @@ function strands(p5, fn) {
6565

6666
p5.Shader.prototype.modify = function(shaderModifier, scope = {}) {
6767
if (shaderModifier instanceof Function) {
68-
// Reset the context object every time modify is called;
69-
// const backend = glslBackend;
70-
initStrandsContext(strandsContext, glslBackend, { active: true });
71-
createShaderHooksFunctions(strandsContext, fn, this);
72-
// TODO: expose this, is internal for debugging for now.
73-
const options = { parser: true, srcLocations: false };
68+
try {
69+
// Reset the context object every time modify is called;
70+
// const backend = glslBackend;
71+
initStrandsContext(strandsContext, glslBackend, { active: true });
72+
createShaderHooksFunctions(strandsContext, fn, this);
73+
// TODO: expose this, is internal for debugging for now.
74+
const options = { parser: true, srcLocations: false };
7475

75-
// 1. Transpile from strands DSL to JS
76-
let strandsCallback;
77-
if (options.parser) {
78-
// #7955 Wrap function declaration code in brackets so anonymous functions are not top level statements, which causes an error in acorn when parsing
79-
// https://github.com/acornjs/acorn/issues/1385
80-
const sourceString = `(${shaderModifier.toString()})`;
81-
strandsCallback = transpileStrandsToJS(p5, sourceString, options.srcLocations, scope);
82-
} else {
83-
strandsCallback = shaderModifier;
84-
}
85-
86-
// 2. Build the IR from JavaScript API
87-
const globalScope = createBasicBlock(strandsContext.cfg, BlockType.GLOBAL);
88-
pushBlock(strandsContext.cfg, globalScope);
89-
strandsCallback();
90-
popBlock(strandsContext.cfg);
76+
// 1. Transpile from strands DSL to JS
77+
let strandsCallback;
78+
if (options.parser) {
79+
// #7955 Wrap function declaration code in brackets so anonymous functions are not top level statements, which causes an error in acorn when parsing
80+
// https://github.com/acornjs/acorn/issues/1385
81+
const sourceString = `(${shaderModifier.toString()})`;
82+
strandsCallback = transpileStrandsToJS(p5, sourceString, options.srcLocations, scope);
83+
} else {
84+
strandsCallback = shaderModifier;
85+
}
9186

92-
// 3. Generate shader code hooks object from the IR
93-
// .......
94-
const hooksObject = generateShaderCode(strandsContext);
87+
// 2. Build the IR from JavaScript API
88+
const globalScope = createBasicBlock(strandsContext.cfg, BlockType.GLOBAL);
89+
pushBlock(strandsContext.cfg, globalScope);
90+
strandsCallback();
91+
popBlock(strandsContext.cfg);
9592

96-
// Reset the strands runtime context
97-
deinitStrandsContext(strandsContext);
93+
// 3. Generate shader code hooks object from the IR
94+
// .......
95+
const hooksObject = generateShaderCode(strandsContext);
9896

99-
// Call modify with the generated hooks object
100-
return oldModify.call(this, hooksObject);
97+
// Call modify with the generated hooks object
98+
return oldModify.call(this, hooksObject);
99+
} finally {
100+
// Reset the strands runtime context
101+
deinitStrandsContext(strandsContext);
102+
}
101103
}
102104
else {
103105
return oldModify.call(this, shaderModifier)

src/strands/strands_api.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,18 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
205205
const originalp5Fn = fn[typeInfo.fnName];
206206
fn[typeInfo.fnName] = function(...args) {
207207
if (strandsContext.active) {
208-
const { id, dimension } = build.primitiveConstructorNode(strandsContext, typeInfo, args);
209-
return createStrandsNode(id, dimension, strandsContext);
208+
if (args.length === 1 && args[0].dimension && args[0].dimension === typeInfo.dimension) {
209+
const { id, dimension } = build.functionCallNode(strandsContext, typeInfo.fnName, args, {
210+
overloads: [{
211+
params: [args[0].typeInfo()],
212+
returnType: typeInfo,
213+
}]
214+
});
215+
return createStrandsNode(id, dimension, strandsContext);
216+
} else {
217+
const { id, dimension } = build.primitiveConstructorNode(strandsContext, typeInfo, args);
218+
return createStrandsNode(id, dimension, strandsContext);
219+
}
210220
} else if (originalp5Fn) {
211221
return originalp5Fn.apply(this, args);
212222
} else {

src/strands/strands_node.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,21 @@ export class StrandsNode {
1313
const nodeData = getNodeDataFromID(dag, this.id);
1414
if (nodeData && nodeData.identifier) {
1515
this._originalIdentifier = nodeData.identifier;
16+
}
17+
if (nodeData) {
1618
this._originalBaseType = nodeData.baseType;
1719
this._originalDimension = nodeData.dimension;
1820
}
1921
}
2022
copy() {
2123
return createStrandsNode(this.id, this.dimension, this.strandsContext);
2224
}
25+
typeInfo() {
26+
return {
27+
baseType: this._originalBaseType || BaseType.FLOAT,
28+
dimension: this.dimension
29+
};
30+
}
2331
bridge(value) {
2432
const { dag, cfg } = this.strandsContext;
2533
const orig = getNodeDataFromID(dag, this.id);
@@ -30,8 +38,8 @@ export class StrandsNode {
3038
newValueID = value.id;
3139
} else {
3240
const newVal = primitiveConstructorNode(
33-
this.strandsContext,
34-
{ baseType, dimension: this.dimension },
41+
this.strandsContext,
42+
{ baseType, dimension: this.dimension },
3543
value
3644
);
3745
newValueID = newVal.id;
@@ -85,8 +93,8 @@ export class StrandsNode {
8593
newValueID = value.id;
8694
} else {
8795
const newVal = primitiveConstructorNode(
88-
this.strandsContext,
89-
{ baseType, dimension: this.dimension },
96+
this.strandsContext,
97+
{ baseType, dimension: this.dimension },
9098
value
9199
);
92100
newValueID = newVal.id;
@@ -159,4 +167,4 @@ export function createStrandsNode(id, dimension, strandsContext, onRebind) {
159167
new StrandsNode(id, dimension, strandsContext),
160168
swizzleTrap(id, dimension, strandsContext, onRebind)
161169
);
162-
}
170+
}

src/type/p5.Font.js

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -737,25 +737,14 @@ export class Font {
737737

738738
/////////////////////////////// HELPERS ////////////////////////////////
739739

740-
_verticalAlign(size) {
741-
const { sCapHeight } = this.data?.['OS/2'] || {};
742-
const { unitsPerEm = 1000 } = this.data?.head || {};
743-
const { ascender = 0, descender = 0 } = this.data?.hhea || {};
744-
const current = ascender / 2;
745-
const target = (sCapHeight || (ascender + descender)) / 2;
746-
const offset = target - current;
747-
return offset * size / unitsPerEm;
748-
}
749-
750740
/*
751741
Returns an array of line objects, each containing { text, x, y, glyphs: [ {g, path} ] }
752742
*/
753743
_lineateAndPathify(str, x, y, width, height, options = {}) {
754744

755745
let renderer = options?.graphics?._renderer || this._pInst._renderer;
756-
757-
// save the baseline
758-
let setBaseline = renderer.drawingContext.textBaseline;
746+
renderer.push();
747+
renderer.textFont(this);
759748

760749
// lineate and compute bounds for the text
761750
let { lines, bounds } = renderer._computeBounds
@@ -772,8 +761,7 @@ export class Font {
772761
const axs = this._currentAxes(renderer);
773762
let pathsForLine = lines.map(l => this._lineToGlyphs(l, { scale, axs }));
774763

775-
// restore the baseline
776-
renderer.drawingContext.textBaseline = setBaseline;
764+
renderer.pop();
777765

778766
return pathsForLine;
779767
}
@@ -857,7 +845,7 @@ export class Font {
857845

858846
_position(renderer, lines, bounds, width, height) {
859847

860-
let { textAlign, textLeading } = renderer.states;
848+
let { textAlign, textLeading, textSize } = renderer.states;
861849
let metrics = this._measureTextDefault(renderer, 'X');
862850
let ascent = metrics.fontBoundingBoxAscent;
863851

src/type/textCore.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2688,6 +2688,13 @@ function textCore(p5, fn) {
26882688
return this._yAlignOffset(lineData, adjustedH);
26892689
};
26902690

2691+
p5.RendererGL.prototype._verticalAlignFont = function() {
2692+
const ctx = this.textDrawingContext();
2693+
const metrics = ctx.measureText('X');
2694+
return -metrics.alphabeticBaseline ||
2695+
(-metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent);
2696+
}
2697+
26912698
p5.RendererGL.prototype._yAlignOffset = function (dataArr, height) {
26922699

26932700
if (typeof height === 'undefined') {
@@ -2700,12 +2707,12 @@ function textCore(p5, fn) {
27002707
((textLeading - textSize) * (numLines - 1));
27012708
switch (textBaseline) { // drawingContext ?
27022709
case fn.TOP:
2703-
yOff = textSize;
2710+
yOff = this._verticalAlignFont();
27042711
break;
27052712
case fn.BASELINE:
27062713
break;
27072714
case textCoreConstants._CTX_MIDDLE:
2708-
yOff = -totalHeight / 2 + textSize + (height || 0) / 2;
2715+
yOff = (-totalHeight + textSize + (height || 0)) / 2 + this._verticalAlignFont();
27092716
break;
27102717
case fn.BOTTOM:
27112718
yOff = -(totalHeight - textSize) + (height || 0);
@@ -2714,7 +2721,6 @@ function textCore(p5, fn) {
27142721
console.warn(`${textBaseline} is not supported in WebGL mode.`); // FES?
27152722
break;
27162723
}
2717-
yOff += this.states.textFont.font?._verticalAlign(textSize) || 0; // Does this function exist?
27182724
dataArr.forEach(ele => ele.y += yOff);
27192725
return dataArr;
27202726
};

src/webgl/p5.Shader.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ class Shader {
533533
// if our vertex shader failed compilation?
534534
if (!gl.getShaderParameter(this._vertShader, gl.COMPILE_STATUS)) {
535535
const glError = gl.getShaderInfoLog(this._vertShader);
536-
if (typeof IS_MINIFIED !== 'undefined') {
536+
if (typeof IS_MINIFIED !== 'undefined' || typeof p5 === 'undefined') {
537537
console.error(glError);
538538
} else {
539539
p5._friendlyError(
@@ -551,7 +551,7 @@ class Shader {
551551
// if our frag shader failed compilation?
552552
if (!gl.getShaderParameter(this._fragShader, gl.COMPILE_STATUS)) {
553553
const glError = gl.getShaderInfoLog(this._fragShader);
554-
if (typeof IS_MINIFIED !== 'undefined') {
554+
if (typeof IS_MINIFIED !== 'undefined' || typeof p5 === 'undefined') {
555555
console.error(glError);
556556
} else {
557557
p5._friendlyError(

test/unit/visual/cases/webgl.js

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -692,7 +692,7 @@ visualSuite('WebGL', function() {
692692
});
693693
});
694694

695-
visualSuite('textures in p5.strands', async () => {
695+
visualSuite('textures in p5.strands', () => {
696696
visualTest('uniformTexture() works', async (p5, screenshot) => {
697697
p5.createCanvas(50, 50, p5.WEBGL);
698698
const tex = await p5.loadImage('/unit/assets/cat.jpg');
@@ -708,4 +708,68 @@ visualSuite('WebGL', function() {
708708
screenshot();
709709
});
710710
});
711+
712+
visualSuite('instanced randering', async () => {
713+
visualTest('can draw in a grid with floor()', (p5, screenshot) => {
714+
p5.createCanvas(50, 50, p5.WEBGL);
715+
const shader = p5.baseMaterialShader().modify(() => {
716+
p5.getWorldInputs((inputs) => {
717+
const id = p5.instanceID();
718+
const gridSize = 5;
719+
const row = p5.floor(id / gridSize);
720+
const col = id - row * gridSize;
721+
const blockInnerSize = 10;
722+
const x = (col - gridSize / 2.0) * blockInnerSize + blockInnerSize/2;
723+
const y = (gridSize / 2.0 - row) * blockInnerSize - blockInnerSize/2;
724+
inputs.position += [x, y, 0];
725+
return inputs;
726+
});
727+
}, { p5 });
728+
p5.shader(shader);
729+
const obj = p5.buildGeometry(() => p5.circle(0, 0, 6))
730+
p5.noStroke();
731+
p5.fill(0);
732+
p5.shader(shader);
733+
p5.model(obj, 25);
734+
screenshot();
735+
});
736+
737+
visualTest('can draw in a grid with int()', (p5, screenshot) => {
738+
p5.createCanvas(50, 50, p5.WEBGL);
739+
const shader = p5.baseMaterialShader().modify(() => {
740+
p5.getWorldInputs((inputs) => {
741+
const id = p5.instanceID();
742+
const gridSize = 5;
743+
const row = p5.int(id / gridSize);
744+
const col = id - row * gridSize;
745+
const blockInnerSize = 10;
746+
const x = (col - gridSize / 2.0) * blockInnerSize + blockInnerSize/2;
747+
const y = (gridSize / 2.0 - row) * blockInnerSize - blockInnerSize/2;
748+
inputs.position += [x, y, 0];
749+
return inputs;
750+
});
751+
}, { p5 });
752+
p5.shader(shader);
753+
const obj = p5.buildGeometry(() => p5.circle(0, 0, 6))
754+
p5.noStroke();
755+
p5.fill(0);
756+
p5.shader(shader);
757+
p5.model(obj, 25);
758+
screenshot();
759+
});
760+
});
761+
762+
visualSuite('p5.strands', () => {
763+
visualTest('it recovers from p5.strands errors', (p5, screenshot) => {
764+
p5.createCanvas(50, 50, p5.WEBGL);
765+
try {
766+
p5.baseMaterialShader().modify(() => {
767+
undefined.someMethod(); // This will throw an error
768+
});
769+
} catch (e) {}
770+
p5.background('red');
771+
p5.circle(p5.noise(0), p5.noise(0), 20);
772+
screenshot();
773+
});
774+
});
711775
});
-40 Bytes

0 commit comments

Comments
 (0)