Skip to content

Commit 85c4012

Browse files
Merge pull request #40 from ShaderFrog/stage-support
Adding support for built-in variables for GLSL stages
2 parents 9b054f5 + 9c6f4c9 commit 85c4012

File tree

6 files changed

+131
-5
lines changed

6 files changed

+131
-5
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,18 @@ Where `options` is:
5757

5858
```typescript
5959
type ParserOptions = {
60+
// The stage of the GLSL source, which defines the built in variables when
61+
// parsing, like gl_Position. If specified, when the compiler encounters an
62+
// expected variable for that stage, it will continue on normally (and add
63+
// that variable, like gl_Position, to the current scope). If it encounters an
64+
// unknown variable, it will log a warning or raise an exception, depending
65+
// on if `failOnWarn` is set. If the stage is set to 'either' - used in the
66+
// case when you don't yet know what stage the GLSL is, the compiler *won't*
67+
// warn on built-in variables for *either* stage. If this option is not set,
68+
// the compiler will warn on *all* built-in variables it encounters as not
69+
// defined.
70+
stage: 'vertex' | 'fragment' | 'either';
71+
6072
// Hide warnings. If set to false or not set, then the parser logs warnings
6173
// like undefined functions and variables. If `failOnWarn` is set to true,
6274
// warnings will still cause the parser to raise an error. Defaults to false.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"engines": {
44
"node": ">=16"
55
},
6-
"version": "5.3.2",
6+
"version": "5.4.0",
77
"type": "module",
88
"description": "A GLSL ES 1.0 and 3.0 parser and preprocessor that can preserve whitespace and comments",
99
"scripts": {

src/parser/glsl-grammar.pegjs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
isDeclaredFunction,
2020
isDeclaredType,
2121
xnil,
22-
builtIns
22+
FN_BUILT_INS,
23+
BUILT_INS
2324
} from './grammar.js';
2425

2526
// Apparently peggy can't handle an open curly brace in a string, see
@@ -426,7 +427,7 @@ function_call
426427
const n = node('function_call', { ...identifierPartial, args: args || [], rp });
427428

428429
const isDeclaredFn = isDeclaredFunction(context.scope, fnName);
429-
const isBuiltIn = builtIns.has(fnName);
430+
const isBuiltIn = FN_BUILT_INS.has(fnName);
430431
const isType = isDeclaredType(context.scope, fnName);
431432

432433
// fnName will be undefined here if the identifier is a keyword

src/parser/grammar.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,39 @@ export const leftAssociate = (
102102
head
103103
);
104104

105+
// From https://www.khronos.org/opengl/wiki/Built-in_Variable_(GLSL)
106+
export const BUILT_INS = {
107+
vertex: new Set([
108+
'gl_VertexID',
109+
'gl_InstanceID',
110+
'gl_DrawID',
111+
'gl_BaseVertex',
112+
'gl_BaseInstance',
113+
'gl_Position',
114+
'gl_PointSize',
115+
'gl_ClipDistance',
116+
]),
117+
fragment: new Set([
118+
'gl_FragColor',
119+
'gl_FragData',
120+
'gl_FragCoord',
121+
'gl_FrontFacing',
122+
'gl_PointCoord',
123+
'gl_SampleID',
124+
'gl_SamplePosition',
125+
'gl_SampleMaskIn',
126+
'gl_ClipDistance',
127+
'gl_PrimitiveID',
128+
'gl_Layer',
129+
'gl_ViewportIndex',
130+
'gl_FragDepth',
131+
'gl_SampleMask',
132+
]),
133+
};
134+
105135
// From https://www.khronos.org/registry/OpenGL-Refpages/gl4/index.php
106136
// excluding gl_ prefixed builtins, which don't appear to be functions
107-
export const builtIns = new Set([
137+
export const FN_BUILT_INS = new Set([
108138
'abs',
109139
'acos',
110140
'acosh',
@@ -559,7 +589,17 @@ export const makeLocals = (context: Context) => {
559589
if (foundScope) {
560590
foundScope.bindings[name].references.push(reference);
561591
} else {
562-
warn(`Encountered undefined variable: "${name}"`);
592+
if (
593+
!context.options.stage ||
594+
(context.options.stage === 'vertex' && !BUILT_INS.vertex.has(name)) ||
595+
(context.options.stage === 'fragment' &&
596+
!BUILT_INS.fragment.has(name)) ||
597+
(context.options.stage === 'either' &&
598+
!BUILT_INS.vertex.has(name) &&
599+
!BUILT_INS.fragment)
600+
) {
601+
warn(`Encountered undefined variable: "${name}"`);
602+
}
563603
// This intentionally does not provide a declaration
564604
scope.bindings[name] = makeScopeIndex(reference);
565605
}

src/parser/parse.test.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,3 +440,75 @@ test('exotic precision statements', () => {
440440
.specifier.specifier.token
441441
).toBe('sampler2DRectShadow');
442442
});
443+
444+
test('warns when grammar stage is unknown', () => {
445+
const consoleWarnMock = jest
446+
.spyOn(console, 'warn')
447+
.mockImplementation(() => {});
448+
449+
// we don't know if this is vertex or fragment so it should warn
450+
c.parseSrc(`
451+
void main() {
452+
gl_Position = vec4(0.0);
453+
}
454+
`);
455+
456+
expect(consoleWarnMock).toHaveBeenCalled();
457+
consoleWarnMock.mockRestore();
458+
});
459+
460+
test('does not warn on built in stage variable', () => {
461+
const consoleWarnMock = jest
462+
.spyOn(console, 'warn')
463+
.mockImplementation(() => {});
464+
465+
c.parseSrc(
466+
`
467+
void main() {
468+
gl_Position = vec4(0.0);
469+
}
470+
`,
471+
{ stage: 'vertex' }
472+
);
473+
474+
expect(consoleWarnMock).not.toHaveBeenCalled();
475+
consoleWarnMock.mockRestore();
476+
});
477+
478+
test('does not warn on built in either stage variable', () => {
479+
const consoleWarnMock = jest
480+
.spyOn(console, 'warn')
481+
.mockImplementation(() => {});
482+
483+
c.parseSrc(
484+
`
485+
void main() {
486+
gl_Position = vec4(0.0);
487+
gl_FragColor = vec4(0.0);
488+
}
489+
`,
490+
{ stage: 'either' }
491+
);
492+
493+
expect(consoleWarnMock).not.toHaveBeenCalled();
494+
consoleWarnMock.mockRestore();
495+
});
496+
497+
test('warn on variable from wrong stage', () => {
498+
const consoleWarnMock = jest
499+
.spyOn(console, 'warn')
500+
.mockImplementation(() => {});
501+
502+
c.parseSrc(
503+
`
504+
void main() {
505+
gl_Position = vec4(0.0);
506+
gl_FragColor = vec4(0.0);
507+
}
508+
`,
509+
{ stage: 'fragment' }
510+
);
511+
512+
expect(consoleWarnMock).toHaveBeenCalled();
513+
consoleWarnMock.mockRestore();
514+
});

src/parser/parser.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export type ParserOptions = Partial<{
55
grammarSource: string;
66
includeLocation: boolean;
77
failOnWarn: boolean;
8+
stage: 'vertex' | 'fragment' | 'either';
89
tracer: {
910
trace: (e: {
1011
type: 'rule.enter' | 'rule.match' | 'rule.fail';

0 commit comments

Comments
 (0)