shaderkit 0.0.0 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,411 @@
1
+ // https://registry.khronos.org/OpenGL/specs/es/3.0/GLSL_ES_Specification_3.00.pdf
2
+ // NOTE: restrictions from 5.25-5.26 apply https://registry.khronos.org/webgl/specs/latest/2.0
3
+ export const GLSL_KEYWORDS = [
4
+ // 3.8 Keywords
5
+ 'const',
6
+ 'uniform',
7
+ 'layout',
8
+ 'centroid',
9
+ 'flat',
10
+ 'smooth',
11
+ 'break',
12
+ 'continue',
13
+ 'do',
14
+ 'for',
15
+ 'while',
16
+ 'switch',
17
+ 'case',
18
+ 'default',
19
+ 'if',
20
+ 'else',
21
+ 'in',
22
+ 'out',
23
+ 'inout',
24
+ 'float',
25
+ 'int',
26
+ 'void',
27
+ 'bool',
28
+ 'true',
29
+ 'false',
30
+ 'invariant',
31
+ 'discard',
32
+ 'return',
33
+ 'mat2',
34
+ 'mat3',
35
+ 'mat4',
36
+ 'mat2x2',
37
+ 'mat2x3',
38
+ 'mat2x4',
39
+ 'mat3x2',
40
+ 'mat3x3',
41
+ 'mat3x4',
42
+ 'mat4x2',
43
+ 'mat4x3',
44
+ 'mat4x4',
45
+ 'vec2',
46
+ 'vec3',
47
+ 'vec4',
48
+ 'ivec2',
49
+ 'ivec3',
50
+ 'ivec4',
51
+ 'bvec2',
52
+ 'bvec3',
53
+ 'bvec4',
54
+ 'uint',
55
+ 'uvec2',
56
+ 'uvec3',
57
+ 'uvec4',
58
+ 'lowp',
59
+ 'mediump',
60
+ 'highp',
61
+ 'precision',
62
+ 'sampler2D',
63
+ 'sampler3D',
64
+ 'samplerCube',
65
+ 'sampler2DShadow',
66
+ 'samplerCubeShadow',
67
+ 'sampler2DArray',
68
+ 'sampler2DArrayShadow',
69
+ 'isampler2D',
70
+ 'isampler3D',
71
+ 'isamplerCube',
72
+ 'isampler2DArray',
73
+ 'usampler2D',
74
+ 'usampler3D',
75
+ 'usamplerCube',
76
+ 'usampler2DArray',
77
+ 'struct',
78
+
79
+ // 3.8 Keywords - Reserved for future use
80
+ 'attribute',
81
+ 'varying',
82
+ 'coherent',
83
+ 'volatile',
84
+ 'restrict',
85
+ 'readonly',
86
+ 'writeonly',
87
+ 'resource',
88
+ 'atomic_uint',
89
+ 'noperspective',
90
+ 'patch',
91
+ 'sample',
92
+ 'subroutine',
93
+ 'common',
94
+ 'partition',
95
+ 'active',
96
+ 'asm',
97
+ 'class',
98
+ 'union',
99
+ 'enum',
100
+ 'typedef',
101
+ 'template',
102
+ 'this',
103
+ 'goto',
104
+ 'inline',
105
+ 'noinline',
106
+ 'volatile',
107
+ 'public',
108
+ 'static',
109
+ 'extern',
110
+ 'external',
111
+ 'interface',
112
+ 'long',
113
+ 'short',
114
+ 'double',
115
+ 'half',
116
+ 'fixed',
117
+ 'unsigned',
118
+ 'superp',
119
+ 'input',
120
+ 'output',
121
+ 'hvec2',
122
+ 'hvec3',
123
+ 'hvec4',
124
+ 'dvec2',
125
+ 'dvec3',
126
+ 'dvec4',
127
+ 'fvec2',
128
+ 'fvec3',
129
+ 'fvec4',
130
+ 'sampler3DRect',
131
+ 'filter',
132
+ 'image1D',
133
+ 'image2D',
134
+ 'image3D',
135
+ 'imageCube',
136
+ 'iimage1D',
137
+ 'iimage2D',
138
+ 'iimage3D',
139
+ 'iimageCube',
140
+ 'uimage1D',
141
+ 'uimage2D',
142
+ 'uimage3D',
143
+ 'uimageCube',
144
+ 'image1DArray',
145
+ 'image2DArray',
146
+ 'iimage1DArray',
147
+ 'iimage2DArray',
148
+ 'uimage1DArray',
149
+ 'uimage2DArray',
150
+ 'imageBuffer',
151
+ 'iimageBuffer',
152
+ 'uimageBuffer',
153
+ 'sampler1D',
154
+ 'sampler1DShadow',
155
+ 'sampler1DArray',
156
+ 'sampler1DArrayShadow',
157
+ 'isampler1D',
158
+ 'isampler1DArray',
159
+ 'usampler1D',
160
+ 'usampler1DArray',
161
+ 'sampler2DRect',
162
+ 'sampler2DRectShadow',
163
+ 'isampler2DRect',
164
+ 'usampler2DRect',
165
+ 'samplerBuffer',
166
+ 'isamplerBuffer',
167
+ 'usamplerBuffer',
168
+ 'sampler2DMS',
169
+ 'isampler2DMS',
170
+ 'usampler2DMS',
171
+ 'sampler2DMSArray',
172
+ 'isampler2DMSArray',
173
+ 'usampler2DMSArray',
174
+ 'sizeof',
175
+ 'cast',
176
+ 'namespace',
177
+ 'using',
178
+
179
+ // 3.5 Preprocessor
180
+ '#define',
181
+ '#undef',
182
+ '#if',
183
+ '#ifdef',
184
+ '#ifndef',
185
+ '#else',
186
+ '#elif',
187
+ '#endif',
188
+ '#error',
189
+ '#pragma',
190
+ '#extension',
191
+ '#version',
192
+ '#line',
193
+
194
+ // 3.5 Preprocessor - Operators
195
+ 'defined',
196
+
197
+ // 3.5 Preprocessor - Macros
198
+ '__LINE__',
199
+ '__FILE__',
200
+ '__VERSION__',
201
+ 'GL_ES',
202
+
203
+ // 7.1 Vertex Shader Special Variables
204
+ 'gl_VertexID',
205
+ 'gl_InstanceID',
206
+ 'gl_Position',
207
+ 'gl_PointSize',
208
+
209
+ // 7.2 Fragment Shader Special Variables
210
+ 'gl_FragCoord',
211
+ 'gl_FrontFacing',
212
+ 'gl_FragDepth',
213
+ 'gl_PointCoord',
214
+
215
+ // 7.3 Built-in Constants
216
+ 'gl_MaxVertexAttribs',
217
+ 'gl_MaxVertexUniformVectors',
218
+ 'gl_MaxVertexOutputVectors',
219
+ 'gl_MaxFragmentInputVectors',
220
+ 'gl_MaxVertexTextureImageUnits',
221
+ 'gl_MaxCombinedTextureImageUnits',
222
+ 'gl_MaxTextureImageUnits',
223
+ 'gl_MaxFragmentUniformVectors',
224
+ 'gl_MaxDrawBuffers',
225
+ 'gl_MinProgramTexelOffset',
226
+ 'gl_MaxProgramTexelOffset',
227
+
228
+ // 7.4 Built-in Uniform State
229
+ 'gl_DepthRangeParameters',
230
+ 'gl_DepthRange',
231
+
232
+ // 8.1 Angle and Trigonometry Functions
233
+ 'radians',
234
+ 'degrees',
235
+ 'sin',
236
+ 'cos',
237
+ 'tan',
238
+ 'asin',
239
+ 'acos',
240
+ 'atan',
241
+ 'sinh',
242
+ 'cosh',
243
+ 'tanh',
244
+ 'asinh',
245
+ 'acosh',
246
+ 'atanh',
247
+
248
+ // 8.2 Exponential Functions
249
+ 'pow',
250
+ 'exp',
251
+ 'log',
252
+ 'exp2',
253
+ 'log2',
254
+ 'sqrt',
255
+ 'inversesqrt',
256
+
257
+ // 8.3 Common Functions
258
+ 'abs',
259
+ 'sign',
260
+ 'floor',
261
+ 'trunc',
262
+ 'round',
263
+ 'roundEven',
264
+ 'ceil',
265
+ 'fract',
266
+ 'mod',
267
+ 'modf',
268
+ 'min',
269
+ 'max',
270
+ 'clamp',
271
+ 'mix',
272
+ 'step',
273
+ 'smoothstep',
274
+ 'isnan',
275
+ 'isinf',
276
+ 'floatBitsToInt',
277
+ 'floatBitsToUint',
278
+ 'intBitsToFloat',
279
+ 'uintBitsToFloat',
280
+
281
+ // 8.4 Floating-Point Pack and Unpack Functions
282
+ 'packSnorm2x16',
283
+ 'unpackSnorm2x16',
284
+ 'packUnorm2x16',
285
+ 'unpackUnorm2x16',
286
+ 'packHalf2x16',
287
+ 'unpackHalf2x16',
288
+
289
+ // 8.5 Geometric Functions
290
+ 'length',
291
+ 'distance',
292
+ 'dot',
293
+ 'cross',
294
+ 'normalize',
295
+ 'faceforward',
296
+ 'reflect',
297
+ 'refract',
298
+ 'matrixCompMult',
299
+ 'outerProduct',
300
+ 'transpose',
301
+ 'determinant',
302
+ 'inverse',
303
+
304
+ // 8.7 Vector Relational Functions
305
+ 'lessThan',
306
+ 'lessThanEqual',
307
+ 'greaterThan',
308
+ 'greaterThanEqual',
309
+ 'equal',
310
+ 'notEqual',
311
+ 'any',
312
+ 'all',
313
+ 'not',
314
+
315
+ // 8.8 Texture Lookup Functions
316
+ 'textureSize',
317
+ 'texture',
318
+ 'textureProj',
319
+ 'textureLod',
320
+ 'textureOffset',
321
+ 'texelFetch',
322
+ 'texelFetchOffset',
323
+ 'textureProjOffset',
324
+ 'textureLodOffset',
325
+ 'textureProjLod',
326
+ 'textureProjLodOffset',
327
+ 'textureGrad',
328
+ 'textureGradOffset',
329
+ 'textureProjGrad',
330
+ 'textureProjGradOffset',
331
+
332
+ // 8.9 Fragment Processing Functions
333
+ 'dFdx',
334
+ 'dFdy',
335
+ 'fwidth',
336
+
337
+ // Additional directives
338
+ '#include',
339
+
340
+ // WEBGL_multi_draw https://registry.khronos.org/webgl/extensions/WEBGL_multi_draw
341
+ 'gl_DrawID', // vertex only
342
+
343
+ // OVR_multiview2 https://registry.khronos.org/webgl/extensions/OVR_multiview2
344
+ // OCULUS_multiview https://github.com/KhronosGroup/WebGL/issues/2912
345
+ 'gl_ViewID_OVR',
346
+ 'GL_OVR_multiview2',
347
+ ]
348
+
349
+ // 3.2 Character Set, 5.1 Operators
350
+ export const GLSL_SYMBOLS = [
351
+ // Preprocessor
352
+ '#',
353
+
354
+ // Line continuation
355
+ '\\',
356
+
357
+ // Comments
358
+ '//',
359
+ '/*',
360
+ '*/',
361
+
362
+ // Punctuation
363
+ '.',
364
+ ',',
365
+ ';',
366
+ '{',
367
+ '}',
368
+ '(',
369
+ ')',
370
+ '[',
371
+ ']',
372
+
373
+ // Comparison
374
+ '?',
375
+ ':',
376
+ '<',
377
+ '>',
378
+ '<=',
379
+ '>=',
380
+ '&&',
381
+ '||',
382
+
383
+ // Modifier
384
+ '~',
385
+ '=',
386
+ '!',
387
+ '+',
388
+ '-',
389
+ '/',
390
+ '&',
391
+ '|',
392
+ '^',
393
+ '%',
394
+ '<<',
395
+ '>>',
396
+
397
+ // Operators
398
+ '++',
399
+ '--',
400
+ '==',
401
+ '!=',
402
+ '+=',
403
+ '-=',
404
+ '/=',
405
+ '&=',
406
+ '|=',
407
+ '^=',
408
+ '%=',
409
+ '<<=',
410
+ '>>=',
411
+ ]
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './constants'
2
+ export * from './minifier'
3
+ export * from './tokenizer'
@@ -0,0 +1,121 @@
1
+ import { type Token, tokenize } from './tokenizer'
2
+
3
+ export type MangleMatcher = (token: Token, index: number, tokens: Token[]) => boolean
4
+
5
+ export interface MinifyOptions {
6
+ /** Whether to rename variables. Will call a {@link MangleMatcher} if specified. Default is `false`. */
7
+ mangle: boolean | MangleMatcher
8
+ /** A map to read and write renamed variables to when mangling. */
9
+ mangleMap: Map<string, string>
10
+ /** Whether to rename external variables such as uniforms or varyings. Default is `false`. */
11
+ mangleExternals: boolean
12
+ /** Whether to rename properties in structs or uniform buffers. Default is `false`. */
13
+ mangleProperties: boolean
14
+ }
15
+
16
+ /**
17
+ * Minifies a string of GLSL code.
18
+ */
19
+ export function minify(
20
+ code: string,
21
+ {
22
+ mangle = false,
23
+ mangleMap = new Map(),
24
+ mangleExternals = false,
25
+ mangleProperties = false,
26
+ }: Partial<MinifyOptions> = {},
27
+ ): string {
28
+ const exclude = new Set<string>(mangleMap.values())
29
+ const tokens: Token[] = tokenize(code).filter((token) => token.type !== 'comment')
30
+
31
+ let mangleIndex: number = 0
32
+ let blockIndex: number | null = null
33
+ let prefix: string | null = null
34
+ let minified: string = ''
35
+ for (let i = 0; i < tokens.length; i++) {
36
+ const token = tokens[i]
37
+ if (/keyword|identifier/.test(token.type)) {
38
+ if (/keyword|identifier/.test(tokens[i - 1]?.type)) minified += ' '
39
+
40
+ // Resolve nested keys from members or accessors
41
+ let key = token.value
42
+ if (prefix) {
43
+ key = `${prefix}.${token.value}`
44
+ } else if (tokens[i - 1]?.value === '.' && tokens[i - 2]?.type !== 'symbol') {
45
+ key = `${tokens[i - 2]?.value}.${token.value}`
46
+ }
47
+
48
+ // Mangle declarations and their references
49
+ let renamed = mangleMap.get(key)
50
+ if (
51
+ // no-op
52
+ !renamed &&
53
+ // Filter variable names
54
+ key !== 'main' &&
55
+ (typeof mangle === 'boolean' ? mangle : mangle(token, i, tokens)) &&
56
+ // Is declaration, reference, namespace, or comma-separated list
57
+ token.type === 'identifier' &&
58
+ (/keyword|identifier/.test(tokens[i - 1]?.type) || /}|,/.test(tokens[i - 1]?.value))
59
+ ) {
60
+ if (
61
+ // Skip struct properties when specified
62
+ (!prefix || mangleProperties) &&
63
+ // Skip shader externals when disabled
64
+ (blockIndex != null
65
+ ? // Struct member
66
+ (mangleExternals && mangleProperties) ||
67
+ !/(struct|uniform|in|out|attribute|varying)/.test(tokens[blockIndex - 1]?.value)
68
+ : // Struct header, fully specified uniform, or comma-separated list
69
+ mangleExternals ||
70
+ (!/(uniform|in|out|attribute|varying|,)/.test(tokens[i - 1]?.value) &&
71
+ !/(uniform|in|out|attribute|varying)/.test(tokens[i - 2]?.value)))
72
+ ) {
73
+ while (!renamed || exclude.has(renamed)) {
74
+ renamed = ''
75
+ mangleIndex++
76
+
77
+ let j = mangleIndex
78
+ while (j > 0) {
79
+ renamed = String.fromCharCode(97 + ((j % 26) - 1)) + renamed
80
+ j = Math.floor(j / 26)
81
+ }
82
+ }
83
+
84
+ mangleMap.set(key, renamed)
85
+ }
86
+
87
+ // Start or stop capturing namespaces, prefer UBO suffix if specified.
88
+ // If UBOs don't specify a suffix, their inner declarations are global
89
+ if (tokens[i + 1]?.value === '{') {
90
+ let j = i
91
+ while (tokens[j].value !== '}') j++
92
+ const suffix = tokens[j + 1].value
93
+ if (suffix !== ';') prefix = suffix
94
+ blockIndex = i
95
+ } else if (tokens[i + 2]?.value === '}') {
96
+ prefix = null
97
+ blockIndex = null
98
+ }
99
+ }
100
+
101
+ minified += renamed ?? token.value
102
+ } else if (token.value === '#') {
103
+ // Don't pad consecutive directives
104
+ if (tokens[i - 1]?.value !== '\\') minified += '\n'
105
+
106
+ // Join preprocessor directives
107
+ while (tokens[i].value !== '\\') {
108
+ if ((tokens[i].type !== 'symbol' || tokens[i - 2]?.value === '#') && tokens[i - 1]?.type !== 'symbol')
109
+ minified += ' '
110
+ minified += tokens[i].value
111
+ i++
112
+ }
113
+
114
+ minified += '\n'
115
+ } else {
116
+ minified += token.value
117
+ }
118
+ }
119
+
120
+ return minified.trim()
121
+ }
@@ -0,0 +1,66 @@
1
+ import { GLSL_SYMBOLS, GLSL_KEYWORDS } from './constants'
2
+
3
+ export type TokenType = 'comment' | 'symbol' | 'bool' | 'float' | 'int' | 'uint' | 'identifier' | 'keyword'
4
+
5
+ export interface Token<T = TokenType, V = string> {
6
+ type: T
7
+ value: V
8
+ }
9
+
10
+ /**
11
+ * Tokenizes a string of GLSL code.
12
+ */
13
+ export function tokenize(code: string, index: number = 0): Token[] {
14
+ const tokens: Token[] = []
15
+
16
+ while (index < code.length) {
17
+ let char = code[index]
18
+ let value = ''
19
+
20
+ // Skip whitespace
21
+ if (/\s/.test(char)) {
22
+ index++
23
+ continue
24
+ }
25
+
26
+ // Escape newlines after directives, skip comments
27
+ if (char === '#') {
28
+ let j = index
29
+ while (code[j] !== '\n' && !/\/[\/\*]/.test(code[j] + code[j + 1])) j++
30
+ code = code.substring(0, j) + '\\' + code.substring(j)
31
+ }
32
+
33
+ // Parse values and identifiers
34
+ while ((/\d/.test(char) ? /[\d\.\-\w]/ : /\w/).test(code[index])) value += code[index++]
35
+
36
+ // Parse symbols, combine if able
37
+ if (!value) {
38
+ value = char
39
+ for (const symbol of GLSL_SYMBOLS) {
40
+ if (symbol.length > value.length && code.startsWith(symbol, index)) value = symbol
41
+ }
42
+ index += value.length
43
+ }
44
+
45
+ // Label and add token
46
+ if (/\/[\/\*]/.test(value)) {
47
+ const isMultiline = value === '/*'
48
+ while (!value.endsWith(isMultiline ? '*/' : '\n')) value += code[index++]
49
+ tokens.push({ type: 'comment', value })
50
+ } else if (!/\w/.test(char)) {
51
+ tokens.push({ type: 'symbol', value })
52
+ } else if (/\d/.test(char)) {
53
+ if (/[uU]/.test(value)) tokens.push({ type: 'uint', value })
54
+ else if (/\.|[eE]-?\d/.test(value)) tokens.push({ type: 'float', value })
55
+ else tokens.push({ type: 'int', value })
56
+ } else if (/^(true|false)$/.test(value)) {
57
+ tokens.push({ type: 'bool', value })
58
+ } else if (GLSL_KEYWORDS.includes(tokens[tokens.length - 1]?.value === '#' ? `#${value}` : value)) {
59
+ tokens.push({ type: 'keyword', value })
60
+ } else {
61
+ tokens.push({ type: 'identifier', value })
62
+ }
63
+ }
64
+
65
+ return tokens
66
+ }