Skip to content

Commit b77cb58

Browse files
committed
Handle varyings between hooks in the same shader
1 parent d46a782 commit b77cb58

5 files changed

Lines changed: 97 additions & 9 deletions

File tree

src/strands/strands_api.js

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,28 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) {
149149
strandsContext.uniforms.push({ name, typeInfo, defaultValue });
150150
return createStrandsNode(id, dimension, strandsContext);
151151
};
152-
fn[`varying${pascalTypeName}`] = function(name) {
152+
// Shared variables with smart context detection
153+
fn[`shared${pascalTypeName}`] = function(name) {
153154
const { id, dimension } = build.variableNode(strandsContext, typeInfo, name);
154-
strandsContext.vertexDeclarations.add(`out ${typeInfo.fnName} ${name};`);
155-
strandsContext.fragmentDeclarations.add(`in ${typeInfo.fnName} ${name};`);
155+
156+
// Initialize shared variables tracking if not present
157+
if (!strandsContext.sharedVariables) {
158+
strandsContext.sharedVariables = new Map();
159+
}
160+
161+
// Track this shared variable for smart declaration generation
162+
strandsContext.sharedVariables.set(name, {
163+
typeInfo,
164+
usedInVertex: false,
165+
usedInFragment: false,
166+
declared: false
167+
});
168+
156169
return createStrandsNode(id, dimension, strandsContext);
157170
};
171+
172+
// Alias varying* as shared* for backward compatibility
173+
fn[`varying${pascalTypeName}`] = fn[`shared${pascalTypeName}`];
158174
if (pascalTypeName.startsWith('Vec')) {
159175
// For compatibility, also alias uniformVec2 as uniformVector2, what we initially
160176
// documented these as
@@ -270,11 +286,20 @@ function enforceReturnTypeMatch(strandsContext, expectedType, returned, hookName
270286
return returnedNodeID;
271287
}
272288
export function createShaderHooksFunctions(strandsContext, fn, shader) {
289+
// Add shader context to hooks before spreading
290+
const vertexHooksWithContext = Object.fromEntries(
291+
Object.entries(shader.hooks.vertex).map(([name, hook]) => [name, { ...hook, shaderContext: 'vertex' }])
292+
);
293+
const fragmentHooksWithContext = Object.fromEntries(
294+
Object.entries(shader.hooks.fragment).map(([name, hook]) => [name, { ...hook, shaderContext: 'fragment' }])
295+
);
296+
273297
const availableHooks = {
274-
...shader.hooks.vertex,
275-
...shader.hooks.fragment,
298+
...vertexHooksWithContext,
299+
...fragmentHooksWithContext,
276300
}
277301
const hookTypes = Object.keys(availableHooks).map(name => shader.hookTypes(name));
302+
278303
const { cfg, dag } = strandsContext;
279304
for (const hookType of hookTypes) {
280305
const hookImplementation = function(hookUserCallback) {
@@ -326,10 +351,13 @@ export function createShaderHooksFunctions(strandsContext, fn, shader) {
326351
const expectedTypeInfo = TypeInfoFromGLSLName[expectedReturnType.typeName];
327352
rootNodeID = enforceReturnTypeMatch(strandsContext, expectedTypeInfo, userReturned, hookType.name);
328353
}
354+
const fullHookName = `${hookType.returnType.typeName} ${hookType.name}`;
355+
const hookInfo = availableHooks[fullHookName];
329356
strandsContext.hooks.push({
330357
hookType,
331358
entryBlockID,
332359
rootNodeID,
360+
shaderContext: hookInfo?.shaderContext, // 'vertex' or 'fragment'
333361
});
334362
CFG.popBlock(cfg);
335363
}

src/strands/strands_codegen.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function generateShaderCode(strandsContext) {
1818
hooksObj.uniforms[declaration] = defaultValue;
1919
}
2020

21-
for (const { hookType, rootNodeID, entryBlockID } of strandsContext.hooks) {
21+
for (const { hookType, rootNodeID, entryBlockID, shaderContext } of strandsContext.hooks) {
2222
const generationContext = {
2323
indent: 1,
2424
codeLines: [],
@@ -29,6 +29,8 @@ export function generateShaderCode(strandsContext) {
2929
declarations: [],
3030
nextTempID: 0,
3131
visitedNodes: new Set(),
32+
shaderContext, // 'vertex' or 'fragment'
33+
strandsContext, // For shared variable tracking
3234
};
3335

3436
const blocks = sortCFG(cfg.outgoingEdges, entryBlockID);
@@ -56,6 +58,24 @@ export function generateShaderCode(strandsContext) {
5658
hooksObj[`${hookType.returnType.typeName} ${hookType.name}`] = [firstLine, ...generationContext.codeLines, '}'].join('\n');
5759
}
5860

61+
// Finalize shared variable declarations based on usage
62+
if (strandsContext.sharedVariables) {
63+
for (const [varName, varInfo] of strandsContext.sharedVariables) {
64+
if (varInfo.usedInVertex && varInfo.usedInFragment) {
65+
// Used in both shaders - declare as varying
66+
vertexDeclarations.add(`OUT ${varInfo.typeInfo.fnName} ${varName};`);
67+
fragmentDeclarations.add(`IN ${varInfo.typeInfo.fnName} ${varName};`);
68+
} else if (varInfo.usedInVertex) {
69+
// Only used in vertex shader - declare as local variable
70+
vertexDeclarations.add(`${varInfo.typeInfo.fnName} ${varName};`);
71+
} else if (varInfo.usedInFragment) {
72+
// Only used in fragment shader - declare as local variable
73+
fragmentDeclarations.add(`${varInfo.typeInfo.fnName} ${varName};`);
74+
}
75+
// If not used anywhere, don't declare it
76+
}
77+
}
78+
5979
hooksObj.vertexDeclarations = [...vertexDeclarations].join('\n');
6080
hooksObj.fragmentDeclarations = [...fragmentDeclarations].join('\n');
6181

src/strands/strands_glslBackend.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,15 @@ export const glslBackend = {
250250
return node.value;
251251
}
252252
case NodeType.VARIABLE:
253+
// Track shared variable usage context
254+
if (generationContext.shaderContext && generationContext.strandsContext?.sharedVariables?.has(node.identifier)) {
255+
const sharedVar = generationContext.strandsContext.sharedVariables.get(node.identifier);
256+
if (generationContext.shaderContext === 'vertex') {
257+
sharedVar.usedInVertex = true;
258+
} else if (generationContext.shaderContext === 'fragment') {
259+
sharedVar.usedInFragment = true;
260+
}
261+
}
253262
return node.identifier;
254263
case NodeType.OPERATION:
255264
const useParantheses = node.usedBy.length > 0;

src/strands/strands_transpiler.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ function nodeIsVarying(node) {
4545
(
4646
// Global mode
4747
node.callee?.type === 'Identifier' &&
48-
node.callee?.name.startsWith('varying')
48+
(node.callee?.name.startsWith('varying') || node.callee?.name.startsWith('shared'))
4949
) || (
5050
// Instance mode
5151
node.callee?.type === 'MemberExpression' &&
52-
node.callee?.property.name.startsWith('varying')
52+
(node.callee?.property.name.startsWith('varying') || node.callee?.property.name.startsWith('shared'))
5353
)
5454
);
5555
}

test/unit/webgl/p5.Shader.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1070,6 +1070,7 @@ suite('p5.Shader', function() {
10701070
suite('passing data between shaders', () => {
10711071
test('handle passing a value from a vertex hook to a fragment hook', () => {
10721072
myp5.createCanvas(50, 50, myp5.WEBGL);
1073+
myp5.pixelDensity(1);
10731074

10741075
const testShader = myp5.baseMaterialShader().modify(() => {
10751076
let worldPos = myp5.varyingVec3();
@@ -1100,8 +1101,9 @@ suite('p5.Shader', function() {
11001101
assert.approximately(cornerColor[2], 0, 5);
11011102
});
11021103

1103-
test('handle passing a value from a vertex hook to a fragment hook', () => {
1104+
test('handle passing a value from a vertex hook to a fragment hook with swizzle assignment', () => {
11041105
myp5.createCanvas(50, 50, myp5.WEBGL);
1106+
myp5.pixelDensity(1);
11051107

11061108
const testShader = myp5.baseMaterialShader().modify(() => {
11071109
let worldPos = myp5.varyingVec3();
@@ -1134,6 +1136,7 @@ suite('p5.Shader', function() {
11341136

11351137
test('handle passing a value from a vertex hook to a fragment hook as part of hook output', () => {
11361138
myp5.createCanvas(50, 50, myp5.WEBGL);
1139+
myp5.pixelDensity(1);
11371140

11381141
const testShader = myp5.baseMaterialShader().modify(() => {
11391142
let worldPos = myp5.varyingVec3();
@@ -1164,6 +1167,34 @@ suite('p5.Shader', function() {
11641167
assert.approximately(cornerColor[1], 255, 5);
11651168
assert.approximately(cornerColor[2], 0, 5);
11661169
});
1170+
1171+
test('handle passing a value between fragment hooks only', () => {
1172+
myp5.createCanvas(50, 50, myp5.WEBGL);
1173+
myp5.pixelDensity(1);
1174+
1175+
const testShader = myp5.baseMaterialShader().modify(() => {
1176+
let processedNormal = myp5.sharedVec3();
1177+
myp5.getPixelInputs((inputs) => {
1178+
processedNormal = myp5.normalize(inputs.normal);
1179+
return inputs;
1180+
});
1181+
myp5.getFinalColor((c) => {
1182+
// Use the processed normal to create a color - should be [0, 0, 1] for plane facing camera
1183+
return [myp5.abs(processedNormal), 1];
1184+
});
1185+
}, { myp5 });
1186+
1187+
myp5.background(255, 0, 0); // Red background to distinguish from result
1188+
myp5.noStroke();
1189+
myp5.shader(testShader);
1190+
myp5.plane(myp5.width, myp5.height);
1191+
1192+
// Normal of plane facing camera should be [0, 0, 1], so color should be [0, 0, 255]
1193+
const centerColor = myp5.get(25, 25);
1194+
assert.approximately(centerColor[0], 0, 5); // Red component
1195+
assert.approximately(centerColor[1], 0, 5); // Green component
1196+
assert.approximately(centerColor[2], 255, 5); // Blue component
1197+
});
11671198
});
11681199

11691200
suite('filter shader hooks', () => {

0 commit comments

Comments
 (0)