Skip to content

Commit

Permalink
Merge pull request #40 from ShaderFrog/stage-support
Browse files Browse the repository at this point in the history
Adding support for built-in variables for GLSL stages
  • Loading branch information
AndrewRayCode authored Jan 16, 2025
2 parents 9b054f5 + 9c6f4c9 commit 85c4012
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 5 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ Where `options` is:

```typescript
type ParserOptions = {
// The stage of the GLSL source, which defines the built in variables when
// parsing, like gl_Position. If specified, when the compiler encounters an
// expected variable for that stage, it will continue on normally (and add
// that variable, like gl_Position, to the current scope). If it encounters an
// unknown variable, it will log a warning or raise an exception, depending
// on if `failOnWarn` is set. If the stage is set to 'either' - used in the
// case when you don't yet know what stage the GLSL is, the compiler *won't*
// warn on built-in variables for *either* stage. If this option is not set,
// the compiler will warn on *all* built-in variables it encounters as not
// defined.
stage: 'vertex' | 'fragment' | 'either';

// Hide warnings. If set to false or not set, then the parser logs warnings
// like undefined functions and variables. If `failOnWarn` is set to true,
// warnings will still cause the parser to raise an error. Defaults to false.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"engines": {
"node": ">=16"
},
"version": "5.3.2",
"version": "5.4.0",
"type": "module",
"description": "A GLSL ES 1.0 and 3.0 parser and preprocessor that can preserve whitespace and comments",
"scripts": {
Expand Down
5 changes: 3 additions & 2 deletions src/parser/glsl-grammar.pegjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
isDeclaredFunction,
isDeclaredType,
xnil,
builtIns
FN_BUILT_INS,
BUILT_INS
} from './grammar.js';

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

const isDeclaredFn = isDeclaredFunction(context.scope, fnName);
const isBuiltIn = builtIns.has(fnName);
const isBuiltIn = FN_BUILT_INS.has(fnName);
const isType = isDeclaredType(context.scope, fnName);

// fnName will be undefined here if the identifier is a keyword
Expand Down
44 changes: 42 additions & 2 deletions src/parser/grammar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,39 @@ export const leftAssociate = (
head
);

// From https://www.khronos.org/opengl/wiki/Built-in_Variable_(GLSL)
export const BUILT_INS = {
vertex: new Set([
'gl_VertexID',
'gl_InstanceID',
'gl_DrawID',
'gl_BaseVertex',
'gl_BaseInstance',
'gl_Position',
'gl_PointSize',
'gl_ClipDistance',
]),
fragment: new Set([
'gl_FragColor',
'gl_FragData',
'gl_FragCoord',
'gl_FrontFacing',
'gl_PointCoord',
'gl_SampleID',
'gl_SamplePosition',
'gl_SampleMaskIn',
'gl_ClipDistance',
'gl_PrimitiveID',
'gl_Layer',
'gl_ViewportIndex',
'gl_FragDepth',
'gl_SampleMask',
]),
};

// From https://www.khronos.org/registry/OpenGL-Refpages/gl4/index.php
// excluding gl_ prefixed builtins, which don't appear to be functions
export const builtIns = new Set([
export const FN_BUILT_INS = new Set([
'abs',
'acos',
'acosh',
Expand Down Expand Up @@ -559,7 +589,17 @@ export const makeLocals = (context: Context) => {
if (foundScope) {
foundScope.bindings[name].references.push(reference);
} else {
warn(`Encountered undefined variable: "${name}"`);
if (
!context.options.stage ||
(context.options.stage === 'vertex' && !BUILT_INS.vertex.has(name)) ||
(context.options.stage === 'fragment' &&
!BUILT_INS.fragment.has(name)) ||
(context.options.stage === 'either' &&
!BUILT_INS.vertex.has(name) &&
!BUILT_INS.fragment)
) {
warn(`Encountered undefined variable: "${name}"`);
}
// This intentionally does not provide a declaration
scope.bindings[name] = makeScopeIndex(reference);
}
Expand Down
72 changes: 72 additions & 0 deletions src/parser/parse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,3 +440,75 @@ test('exotic precision statements', () => {
.specifier.specifier.token
).toBe('sampler2DRectShadow');
});

test('warns when grammar stage is unknown', () => {
const consoleWarnMock = jest
.spyOn(console, 'warn')
.mockImplementation(() => {});

// we don't know if this is vertex or fragment so it should warn
c.parseSrc(`
void main() {
gl_Position = vec4(0.0);
}
`);

expect(consoleWarnMock).toHaveBeenCalled();
consoleWarnMock.mockRestore();
});

test('does not warn on built in stage variable', () => {
const consoleWarnMock = jest
.spyOn(console, 'warn')
.mockImplementation(() => {});

c.parseSrc(
`
void main() {
gl_Position = vec4(0.0);
}
`,
{ stage: 'vertex' }
);

expect(consoleWarnMock).not.toHaveBeenCalled();
consoleWarnMock.mockRestore();
});

test('does not warn on built in either stage variable', () => {
const consoleWarnMock = jest
.spyOn(console, 'warn')
.mockImplementation(() => {});

c.parseSrc(
`
void main() {
gl_Position = vec4(0.0);
gl_FragColor = vec4(0.0);
}
`,
{ stage: 'either' }
);

expect(consoleWarnMock).not.toHaveBeenCalled();
consoleWarnMock.mockRestore();
});

test('warn on variable from wrong stage', () => {
const consoleWarnMock = jest
.spyOn(console, 'warn')
.mockImplementation(() => {});

c.parseSrc(
`
void main() {
gl_Position = vec4(0.0);
gl_FragColor = vec4(0.0);
}
`,
{ stage: 'fragment' }
);

expect(consoleWarnMock).toHaveBeenCalled();
consoleWarnMock.mockRestore();
});
1 change: 1 addition & 0 deletions src/parser/parser.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type ParserOptions = Partial<{
grammarSource: string;
includeLocation: boolean;
failOnWarn: boolean;
stage: 'vertex' | 'fragment' | 'either';
tracer: {
trace: (e: {
type: 'rule.enter' | 'rule.match' | 'rule.fail';
Expand Down

0 comments on commit 85c4012

Please sign in to comment.