Skip to content

Commit

Permalink
feat: GLSL ES 3.1 support (#29)
Browse files Browse the repository at this point in the history
  • Loading branch information
CodyJasonBennett authored Feb 16, 2025
1 parent 4bdd969 commit 43d13ad
Show file tree
Hide file tree
Showing 9 changed files with 506 additions and 98 deletions.
47 changes: 30 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ Tools and IntelliSense for GLSL and WGSL.
- [BlockStatement](#blockstatement)
- [DiscardStatement](#discardstatement)
- [PreprocessorStatement](#preprocessorstatement)
- [PrecisionStatement](#precisionstatement)
- [InvariantStatement](#invariantstatement)
- [PrecisionQualifierStatement](#precisionqualifierstatement)
- [InvariantQualifierStatement](#invariantqualifierstatement)
- [LayoutQualifierStatement](#layoutqualifierstatement)
- Control Flow
- [ReturnStatement](#returnstatement)
- [BreakStatement](#breakstatement)
Expand All @@ -44,7 +45,7 @@ Tools and IntelliSense for GLSL and WGSL.
- [FunctionParameter](#functionparameter)
- [VariableDeclaration](#variabledeclaration)
- [VariableDeclarator](#variabledeclarator)
- [UniformDeclarationBlock](#uniformdeclarationblock)
- [StructuredBufferDeclaration](#structuredbufferdeclaration)
- [StructDeclaration](#structdeclaration)
- Expressions
- [ArrayExpression](#arrayexpression)
Expand Down Expand Up @@ -395,29 +396,41 @@ interface PreprocessorStatement extends Node {
}
```

### PrecisionStatement
### PrecisionQualifierStatement

A GLSL precision statement.
A GLSL precision qualifier statement.

```ts
interface PrecisionStatement extends Node {
type: 'PrecisionStatement'
interface PrecisionQualifierStatement extends Node {
type: 'PrecisionQualifierStatement'
precision: PrecisionQualifier
typeSpecifier: Identifier
}
```

### InvariantStatement
### InvariantQualifierStatement

A GLSL invariant statement.
A GLSL invariant qualifier statement.

```ts
interface InvariantStatement extends Node {
type: 'InvariantStatement'
interface InvariantQualifierStatement extends Node {
type: 'InvariantQualifierStatement'
typeSpecifier: Identifier
}
```

### LayoutQualifierStatement

A layout qualifier statement.

```ts
interface LayoutQualifierStatement extends Node {
type: 'LayoutQualifierStatement'
layout: Record<string, string | boolean>
qualifier: StorageQualifier
}
```

### ReturnStatement

A return statement with an optional argument.
Expand Down Expand Up @@ -546,7 +559,7 @@ A function parameter within a function declaration.
```ts
interface FunctionParameter extends Node {
type: 'FunctionParameter'
id: Identifier
id: Identifier | null
qualifiers: (ConstantQualifier | ParameterQualifier | PrecisionQualifier)[]
typeSpecifier: Identifier | ArraySpecifier
}
Expand Down Expand Up @@ -578,15 +591,15 @@ interface VariableDeclarator extends Node {
}
```

### UniformDeclarationBlock
### StructuredBufferDeclaration

A uniform declaration block with optional layout and qualifiers.
A buffer interface declaration with optional layout and qualifiers.

```ts
interface UniformDeclarationBlock extends Node {
type: 'UniformDeclarationBlock'
interface StructuredBufferDeclaration extends Node {
type: 'StructuredBufferDeclaration'
id: Identifier | null
qualifiers: LayoutQualifier[]
qualifiers: (InterfaceStorageQualifier | MemoryQualifier | LayoutQualifier)[]
typeSpecifier: Identifier | ArraySpecifier
layout: Record<string, string | boolean> | null
members: VariableDeclaration[]
Expand Down
38 changes: 25 additions & 13 deletions src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,8 @@ export interface ForStatement extends Node {
export type ConstantQualifier = 'const'
export type ParameterQualifier = 'in' | 'out' | 'inout'
export type StorageQualifier = 'uniform' | 'in' | 'out'
export type InterfaceStorageQualifier = 'uniform' | 'buffer'
export type MemoryQualifier = 'coherent' | 'volatile' | 'restrict' | 'readonly' | 'writeonly'

export type InterpolationQualifier = 'centroid' | 'smooth' | 'flat' | 'invariant'
export type LayoutQualifier = 'location' | 'std140' | 'packed' | 'shared'
Expand All @@ -297,7 +299,7 @@ export interface FunctionDeclaration extends Node {
*/
export interface FunctionParameter extends Node {
type: 'FunctionParameter'
id: Identifier
id: Identifier | null
qualifiers: (ConstantQualifier | ParameterQualifier | PrecisionQualifier)[]
typeSpecifier: Identifier | ArraySpecifier
}
Expand Down Expand Up @@ -325,10 +327,10 @@ export interface VariableDeclarator extends Node {
/**
* A uniform declaration block with optional layout and qualifiers.
*/
export interface UniformDeclarationBlock extends Node {
type: 'UniformDeclarationBlock'
export interface StructuredBufferDeclaration extends Node {
type: 'StructuredBufferDeclaration'
id: Identifier | null
qualifiers: LayoutQualifier[]
qualifiers: (InterfaceStorageQualifier | MemoryQualifier | LayoutQualifier)[]
typeSpecifier: Identifier | ArraySpecifier
layout: Record<string, string | boolean> | null
members: VariableDeclaration[]
Expand All @@ -353,22 +355,31 @@ export interface PreprocessorStatement extends Node {
}

/**
* A GLSL precision statement.
* A GLSL precision qualifier statement.
*/
export interface PrecisionStatement extends Node {
type: 'PrecisionStatement'
export interface PrecisionQualifierStatement extends Node {
type: 'PrecisionQualifierStatement'
precision: PrecisionQualifier
typeSpecifier: Identifier
}

/**
* A GLSL invariant statement.
* A GLSL invariant qualifier statement.
*/
export interface InvariantStatement extends Node {
type: 'InvariantStatement'
export interface InvariantQualifierStatement extends Node {
type: 'InvariantQualifierStatement'
typeSpecifier: Identifier
}

/**
* A layout qualifier statement.
*/
export interface LayoutQualifierStatement extends Node {
type: 'LayoutQualifierStatement'
layout: Record<string, string | boolean>
qualifier: StorageQualifier
}

export type Expression =
| Literal
| Identifier
Expand Down Expand Up @@ -396,11 +407,12 @@ export type Statement =
| ForStatement
| FunctionDeclaration
| VariableDeclaration
| UniformDeclarationBlock
| StructuredBufferDeclaration
| StructDeclaration
| PreprocessorStatement
| PrecisionStatement
| InvariantStatement
| PrecisionQualifierStatement
| InvariantQualifierStatement
| LayoutQualifierStatement

export type AST =
| Program
Expand Down
89 changes: 89 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,95 @@ export const GLSL_KEYWORDS = [
// OCULUS_multiview https://github.com/KhronosGroup/WebGL/issues/2912
'gl_ViewID_OVR',
'GL_OVR_multiview2',

// GLSL ES 3.1
// https://registry.khronos.org/OpenGL/specs/es/3.1/GLSL_ES_Specification_3.10.pdf

// 4.3 Storage Qualifiers
'buffer',
'shared',

// 7.1.2 Fragment Shader Special Variables
'gl_HelperInvocation',

// 7.1.3 Compute Shader Special Variables
'gl_NumWorkGroups',
'gl_WorkGroupSize',
'gl_WorkGroupID',
'gl_LocalInvocationID',
'gl_GlobalInvocationID',
'gl_LocalInvocationIndex',

// 7.2 Built-In Constants
'gl_MaxImageUnits',
'gl_MaxVertexImageUniforms',
'gl_MaxFragmentImageUniforms',
'gl_MaxComputeImageUniforms',
'gl_MaxCombinedImageUniforms',
'gl_MaxCombinedShaderOutputResources',
'gl_MaxComputeWorkGroupCount',
'gl_MaxComputeWorkGroupSize',
'gl_MaxComputeUniformComponents',
'gl_MaxComputeTextureImageUnits',
'gl_MaxComputeAtomicCounters',
'gl_MaxComputeAtomicCounterBuffers',
'gl_MaxVertexAtomicCounters',
'gl_MaxFragmentAtomicCounters',
'gl_MaxCombinedAtomicCounters',
'gl_MaxAtomicCounterBindings',
'gl_MaxFragmentAtomicCounterBuffers',
'gl_MaxVertexAtomicCounterBuffers',
'gl_MaxCombinedAtomicCounterBuffers',
'gl_MaxAtomicCounterBufferSize',

// 8 Built-in Functions
'imageSize',
'packUnorm4x8',
'packSnorm4x8',
'unpackUnorm4x8',
'unpackSnorm4x8',

// 8.8 Integer Functions
'bitfieldExtract',
'bitfieldInsert',
'bitfieldReverse',
'bitCount',
'findLSB',
'findMSB',
'uaddCarry',
'usubBorrow',
'umulExtended',
'imulExtended',

// 8.10 Atomic-Counter Functions
'atomicCounterIncrement',
'atomicCounterDecrement',
'atomicCounter',

// 8.11 Atomic Memory Functions
'atomicAdd',
'atomicMin',
'atomicMax',
'atomicAnd',
'atomicOr',
'atomicXor',
'atomicExchange',
'atomicCompSwap',

// 8.12 Image Functions
'imageLoad',
'imageStore',

// 8.14 Shader Invocation Control Functions
'barrier',

// 8.15 Shader Memory Control Functions
'memoryBarrier',
'memoryBarrierAtomicCounter',
'memoryBarrierBuffer',
'memoryBarrierImage',
'memoryBarrierShared',
'groupMemoryBarrier',
]

const SYMBOLS = [
Expand Down
24 changes: 14 additions & 10 deletions src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function format(node: AST | null): string {
case 'Literal':
return node.value
case 'ArraySpecifier':
return `${node.typeSpecifier.name}[${node.dimensions.map(format).join(',')}]`
return `${node.typeSpecifier.name}${node.dimensions.map((d) => `[${format(d)}]`).join('')}`
case 'ExpressionStatement':
return `${format(node.expression)};`
case 'BlockStatement':
Expand All @@ -29,16 +29,18 @@ function format(node: AST | null): string {
let value = ''
if (node.value) {
if (node.name === 'include') value = ` <${format(node.value[0])}>` // three is whitespace sensitive
else if (node.name === 'extension') value = `${node.value.map(format).join(':')}`
else value = ` ${node.value.map(format).join(' ')}`
else if (node.name === 'extension') value = ` ${node.value.map(format).join(':')}`
else if (node.value.length) value = ` ${node.value.map(format).join(' ')}`
}

return `\n#${node.name}${value}\n`
}
case 'PrecisionStatement':
case 'PrecisionQualifierStatement':
return `precision ${node.precision} ${node.typeSpecifier.name};`
case 'InvariantStatement':
case 'InvariantQualifierStatement':
return `invariant ${format(node.typeSpecifier)};`
case 'LayoutQualifierStatement':
return `${formatLayout(node.layout)}${node.qualifier};`
case 'ReturnStatement':
return node.argument ? `return ${format(node.argument)};` : 'return;'
case 'BreakStatement':
Expand Down Expand Up @@ -68,7 +70,8 @@ function format(node: AST | null): string {
}
case 'FunctionParameter': {
const qualifiers = node.qualifiers.length ? `${node.qualifiers.join(' ')} ` : ''
return `${qualifiers}${format(node.typeSpecifier)} ${format(node.id)}`
const id = node.id ? ` ${format(node.id)}` : ''
return `${qualifiers}${format(node.typeSpecifier)}${id}`
}
case 'VariableDeclaration': {
const head = node.declarations[0]
Expand All @@ -80,11 +83,12 @@ function format(node: AST | null): string {
const init = node.init ? `=${format(node.init)}` : ''
return `${format(node.id)}${init}`
}
case 'UniformDeclarationBlock': {
case 'StructuredBufferDeclaration': {
const layout = formatLayout(node.layout)
const qualifiers = node.qualifiers.length ? `${node.qualifiers.join(' ')} ` : ''
const scope = node.id ? `${format(node.id)}` : ''
return `${layout}${qualifiers}${format(node.typeSpecifier)}{${node.members.map(format).join('')}}${scope};`
return `${layout}${node.qualifiers.join(' ')} ${format(node.typeSpecifier)}{${node.members
.map(format)
.join('')}}${scope};`
}
case 'StructDeclaration':
return `struct ${format(node.id)}{${node.members.map(format).join('')}};`
Expand Down Expand Up @@ -120,5 +124,5 @@ export interface GenerateOptions {
* Generates a string of GLSL (WGSL WIP) code from an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree).
*/
export function generate(program: Program, options: GenerateOptions): string {
return format(program).replaceAll('\n\n', '\n').trim()
return format(program).replaceAll('\n\n', '\n').replaceAll('] ', ']').trim()
}
8 changes: 7 additions & 1 deletion src/minifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,21 @@ const isStorage = /* @__PURE__ */ RegExp.prototype.test.bind(
/^(binding|group|layout|uniform|in|out|attribute|varying)$/,
)

const NEWLINE_REGEX = /\\\s+/gm
const DIRECTIVE_REGEX = /(^\s*#[^\\]*?)(\n|\/[\/\*])/gm

/**
* Minifies a string of GLSL or WGSL code.
*/
export function minify(
code: string,
{ mangle = false, mangleMap = new Map(), mangleExternals = false }: Partial<MinifyOptions> = {},
): string {
// Fold newlines
code = code.replace(NEWLINE_REGEX, '')

// Escape newlines after directives, skip comments
code = code.replace(/(^\s*#[^\\]*?)(\n|\/[\/\*])/gm, '$1\\$2')
code = code.replace(DIRECTIVE_REGEX, '$1\\$2')

const mangleCache = new Map()
const tokens: Token[] = tokenize(code).filter((token) => token.type !== 'whitespace' && token.type !== 'comment')
Expand Down
Loading

0 comments on commit 43d13ad

Please sign in to comment.