glslh 0.0.0-stage → 1.0.1

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ ## 📋 `glslh` Changelog
2
+
3
+ ### **v1.0.1** (Latest)
4
+
5
+ - **Strict File Extension Whitelisting**: Added validation checks inside `GLSLHParser` to restrict `#include` directives exclusively to valid shader extensions (`.glsl`, `.glslh`, `.frag`, `.vert`, `.frag.glsl`, `.vert.glsl`), preventing security risks and unauthorized inclusions of non-shader files (e.g., `.py`, `.cpp`, `.txt`).
6
+ - **Compound Header Support**: Enhanced path checking to natively support complex shader extension combinations like `.frag.glslh` and `.vert.glslh`.
7
+ - **Robust Error Messaging**: Improved failure handling and descriptive console warnings/errors when file resolution or extension validation fails.
8
+
9
+ ---
10
+
11
+ ### **v1.0.0**
12
+
13
+ - **Recursive C-Style `#include` Preprocessor**: Introduced core parsing logic enabling `.glslh` and `.glsl` files to modularly include other shader headers recursively with built-in circular dependency protection.
14
+ - **Dynamic JavaScript Math Constant Injection**: Added automatic replacement of JS math expressions (such as `Math.PI` and `Math.TAU`) directly into clean GLSL numeric floats.
15
+ - **Metadata Uniforms Block (`@uniforms`)**: Implemented support for JSON-parsed `@uniforms` comment blocks to automatically generate and format Three.js uniform maps.
16
+ - **Zero-Config DOM Canvas Integration (`GLSLHDomCanvas`)**: Released `autoInitGlslCanvases()` to instantly bind canvas elements to Three.js WebGL renderers with automatic shader loading and animation loops.
package/README.md CHANGED
@@ -1,3 +1,108 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # glslh ⚡
2
+
3
+ > A modular C-style `#include` preprocessor and metadata-driven uniform binder for GLSL shaders, built for zero-boilerplate integration across Vanilla WebGL, Three.js, and React Three Fiber.
4
+
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![JavaScript](https://img.shields.io/badge/language-JavaScript-f7df1e.svg)](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
7
+ [![WebGL / Three.js](https://img.shields.io/badge/graphics-WebGL%20%2F%20Three.js-blue.svg)](https://threejs.org/)
8
+
9
+ ---
10
+
11
+ ## 🎨 Architecture & CPU/GPU Division of Labor
12
+
13
+ `glslh` establishes a clean pipeline separating CPU-side orchestration from GPU-side parallel rendering:
14
+
15
+ ```
16
+ .glslh (Self-Contained Header)
17
+ ┌─────────────────────────────┐
18
+ │ /* @uniforms { ... } */ │ ──► [CPU] Auto-linked JS-to-GLSL Uniforms
19
+ │ #include "utils/noise.glsl"│ ──► [CPU] Recursive File & Dependency Resolution
20
+ │ Math.PI macro injection │ ──► [CPU] JIT Constant Evaluator
21
+ └──────────────┬──────────────┘
22
+ ▼
23
+ [ glslh Preprocessor ]
24
+ ▼
25
+ ┌─────────────────────────────┐
26
+ │ Final Pure GLSL String │ ──► [GPU] Compiled & Executed via WebGL
27
+ └─────────────────────────────┘
28
+
29
+ ```
30
+
31
+ - **CPU-Side (JavaScript):** File fetching, `#include` stitching, uniform metadata parsing, constant macro evaluation (`Math.PI`, `Math.TAU`), and real-time animation loops (`requestAnimationFrame`).
32
+ - **GPU-Side (WebGL / GLSL):** Pure parallel rasterization, vertex positioning, and fragment color blending executing simultaneously across graphics hardware cores.
33
+
34
+ ---
35
+
36
+ ## ✨ Features & Benefits
37
+
38
+ - **Clear CPU/GPU Separation:** All orchestration, math constant calculations (`Math.PI`), and uniform state updates run efficiently on the **CPU**, while heavy rasterization and pixel shading run natively in parallel on the **GPU**.
39
+ - **Zero Runtime Performance Cost:** Because modular files are stitched and compiled once during initialization, your shaders execute at full native GPU speed with zero runtime overhead.
40
+ - **C-Style `#include` Directives:** Split massive shaders into modular files with full circular dependency protection.
41
+ - **Self-Contained Metadata Blocks:** Define uniform data types and default values right at the top of your shader file (`@uniforms`).
42
+ - **Direct JavaScript Math Injection:** Use native JS constants like `Math.PI` or `Math.TAU` directly in your GLSL code; the preprocessor evaluates them before GPU compilation.
43
+ - **Framework Agnostic:** Seamlessly output standard Three.js `ShaderMaterial` objects ready for Vanilla JS, Three.js, or React Three Fiber.
44
+ - **HTML Declarative Support:** Spin up shaders instantly using HTML data attributes (`data-fragment-url="..."`).
45
+
46
+ ---
47
+
48
+ ## 🔍 Understanding the `/* @uniforms */` Block
49
+
50
+ The metadata block is an optional JSON declaration placed at the very top of your `.glslh` header file:
51
+
52
+ ```glsl
53
+ /* @uniforms
54
+ {
55
+ "u_intensity": { "type": "f", "value": 1.0 },
56
+ "u_color": { "type": "c", "value": [1.0, 0.4, 0.0] }
57
+ }
58
+ */
59
+
60
+ ```
61
+
62
+ ### Why use it?
63
+
64
+ In standard WebGL and Three.js, wiring JavaScript variables to shader uniforms requires tedious boilerplate. The `@uniforms` block decentralizes this configuration, letting your shader file declare its own parameters so `glslh` can auto-bind them into Three.js uniform structures instantly.
65
+
66
+ ### What if you forget it?
67
+
68
+ **Nothing breaks.** The parser is completely fault-tolerant. If the comment block is missing, the parser simply returns an empty uniform object (`{}`), and your shader compiles and renders successfully using standard built-in uniforms (like `u_time` and `u_resolution`).
69
+
70
+ - **When to Use:** Use it when your shader needs custom adjustable parameters, colors, or default numerical inputs managed from JavaScript.
71
+ - **When to Omit:** Omit it when your shader is purely procedural, self-contained, or relies solely on built-in time and coordinate attributes.
72
+
73
+ ---
74
+
75
+ ## 🚀 Installation & Quick Start
76
+
77
+ ```bash
78
+ npm install glslh
79
+ ```
80
+
81
+ ### 1. Create a `.glslh` Shader File (`./shaders/header.glslh`)
82
+
83
+ Refer to the complete working example setup here: [GLSLH](./test/shaders/header.glslh)
84
+
85
+ ### 2. Run Declaratively in HTML (`index.html`)
86
+
87
+ Refer to the complete working example setup here: [index.html](./test/index.html)
88
+
89
+ ---
90
+
91
+ ## 📂 Project Structure
92
+
93
+ ```text
94
+ package/
95
+ ├── package.json
96
+ ├── README.md
97
+ └── src/
98
+ ├── parser.js # The C-style #include preprocessor & Math.PI injector
99
+ ├── loader.js # Metadata JSON parser & Three.js uniform binder
100
+ └── dom-canvas.js # HTML data-attribute and canvas auto-loader wrapper
101
+
102
+ ```
103
+
104
+ ---
105
+
106
+ ## 📜 License
107
+
108
+ Distributed under the **MIT License**. See `LICENSE` for more information.
@@ -0,0 +1,10 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24">
2
+ <desc>Created with SVGMaker Editor 3.5.0</desc>
3
+ <defs>
4
+ </defs>
5
+ <path style="stroke: rgb(255,255,255); stroke-opacity: 0; fill: rgb(171,71,188); fill-rule: evenodd" stroke-linecap="round" d="M1.0408 8.824C2.6084 7.0349 5.4668 6.8126 7.591 8.3143 10.9769 10.6363 12.6966 10.6349 15.6607 8.2989 17.5028 6.7643 20.3984 6.9394 22.2957 8.7001 24.193 10.4608 24.448 13.2092 22.8799 14.998 21.3119 16.7868 18.4532 17.0087 16.3293 15.5065 12.9424 13.1865 11.2237 13.1858 8.2607 15.5225 6.4187 17.057 3.5233 16.8821 1.6259 15.1216-0.2714 13.3613-0.5268 10.613 1.0408 8.824Z"/>
6
+ <path style="stroke: rgb(255,255,255); stroke-opacity: 0; fill: rgb(171,71,188); fill-rule: evenodd" stroke-linecap="round" d="M19.8155 7.7205C21.8744 7.7205 23.5435 6.092 23.5435 4.083 23.5435 2.074 21.8744 0.4455 19.8155 0.4455 17.7566 0.4455 16.0876 2.074 16.0876 4.083 16.0876 6.092 17.7566 7.7205 19.8155 7.7205Z"/>
7
+ <path style="stroke: rgb(255,255,255); stroke-opacity: 0; fill: rgb(171,71,188); fill-rule: evenodd" stroke-linecap="round" d="M19.8155 23.5546C21.8744 23.5546 23.5435 21.926 23.5435 19.917 23.5435 17.908 21.8744 16.2795 19.8155 16.2795 17.7566 16.2795 16.0876 17.908 16.0876 19.917 16.0876 21.926 17.7566 23.5546 19.8155 23.5546Z"/>
8
+ <path style="stroke: rgb(255,255,255); stroke-opacity: 0; fill: rgb(171,71,188); fill-rule: evenodd" stroke-linecap="round" d="M4.1845 7.7205C6.2434 7.7205 7.9124 6.092 7.9124 4.083 7.9124 2.074 6.2434 0.4454 4.1845 0.4454 2.1256 0.4454 0.4565 2.074 0.4565 4.083 0.4565 6.092 2.1256 7.7205 4.1845 7.7205Z"/>
9
+ <path style="stroke: rgb(255,255,255); stroke-opacity: 0; fill: rgb(171,71,188); fill-rule: evenodd" stroke-linecap="round" d="M4.1845 23.5546C6.2434 23.5546 7.9124 21.926 7.9124 19.917 7.9124 17.908 6.2434 16.2795 4.1845 16.2795 2.1256 16.2795 0.4565 17.908 0.4565 19.917 0.4565 21.926 2.1256 23.5546 4.1845 23.5546Z"/>
10
+ </svg>
package/package.json CHANGED
@@ -1,6 +1,34 @@
1
- {
2
- "name": "glslh",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
1
+ {
2
+ "name": "glslh",
3
+ "version": "1.0.1",
4
+ "description": "A lightweight GLSL preprocessor and runtime bridge for modular shader development.",
5
+ "type": "module",
6
+ "main": "./src/dom-canvas.js",
7
+ "exports": {
8
+ ".": "./src/dom-canvas.js",
9
+ "./parser": "./src/parser.js",
10
+ "./loader": "./src/loader.js"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/Sudhanshu-Ambastha/glsl-header.git"
15
+ },
16
+ "bugs": {
17
+ "url": "https://github.com/Sudhanshu-Ambastha/glsl-header/issues"
18
+ },
19
+ "homepage": "https://github.com/Sudhanshu-Ambastha/glsl-header#readme",
20
+ "logo": "./assets/glsl_header.svg",
21
+ "keywords": [
22
+ "glsl",
23
+ "shaders",
24
+ "threejs",
25
+ "preprocessor",
26
+ "webgl",
27
+ "graphics"
28
+ ],
29
+ "author": "Sudhanshu Ambastha",
30
+ "license": "MIT",
31
+ "peerDependencies": {
32
+ "three": "^0.150.0 || ^0.160.0"
33
+ }
34
+ }
@@ -0,0 +1,85 @@
1
+ import * as THREE from "three";
2
+ import { GLSLHLoader } from "./loader.js";
3
+
4
+ export class GLSLHDomCanvas {
5
+ constructor(canvasElement) {
6
+ this.canvas = canvasElement;
7
+ this.loader = new GLSLHLoader();
8
+ this.init();
9
+ }
10
+
11
+ async init() {
12
+ const fragUrl = this.canvas.getAttribute("data-fragment-url");
13
+ const width = parseInt(this.canvas.getAttribute("width")) || 600;
14
+ const height = parseInt(this.canvas.getAttribute("height")) || 400;
15
+
16
+ console.log("GLSLH: Initializing WebGL Renderer...");
17
+ this.renderer = new THREE.WebGLRenderer({
18
+ canvas: this.canvas,
19
+ antialias: true,
20
+ });
21
+ this.renderer.setSize(width, height, false);
22
+
23
+ this.scene = new THREE.Scene();
24
+ this.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
25
+
26
+ const vertexShader = `
27
+ void main() {
28
+ gl_Position = vec4(position, 1.0);
29
+ }
30
+ `;
31
+
32
+ let fragmentShader = "";
33
+ let uniforms = {
34
+ u_time: { value: 0.0 },
35
+ u_resolution: { value: new THREE.Vector2(width, height) },
36
+ };
37
+
38
+ try {
39
+ console.log(`GLSLH: Fetching and parsing shader module -> ${fragUrl}`);
40
+ const shaderModule = await this.loader.loadShaderModule(fragUrl);
41
+ console.log("GLSLH: Shader successfully parsed!", shaderModule);
42
+
43
+ fragmentShader = shaderModule.source;
44
+ uniforms = { ...shaderModule.uniforms, ...uniforms };
45
+ } catch (err) {
46
+ console.error("❌ GLSLH Pipeline Error:", err);
47
+ // Fallback to bright red if loading fails
48
+ fragmentShader = `
49
+ void main() {
50
+ gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
51
+ }
52
+ `;
53
+ }
54
+
55
+ this.uniforms = uniforms;
56
+ this.material = new THREE.ShaderMaterial({
57
+ vertexShader,
58
+ fragmentShader,
59
+ uniforms: this.uniforms,
60
+ });
61
+
62
+ const geometry = new THREE.PlaneGeometry(2, 2);
63
+ this.mesh = new THREE.Mesh(geometry, this.material);
64
+ this.scene.add(this.mesh);
65
+
66
+ this.clock = new THREE.Clock();
67
+ this.animate();
68
+ }
69
+
70
+ animate() {
71
+ requestAnimationFrame(() => this.animate());
72
+
73
+ if (this.uniforms.u_time) {
74
+ this.uniforms.u_time.value = this.clock.getElapsedTime();
75
+ }
76
+
77
+ this.renderer.render(this.scene, this.camera);
78
+ }
79
+ }
80
+
81
+ export function autoInitGlslCanvases(selector = "canvas.glsl-canvas") {
82
+ document
83
+ .querySelectorAll(selector)
84
+ .forEach((canvas) => new GLSLHDomCanvas(canvas));
85
+ }
package/src/loader.js ADDED
@@ -0,0 +1,61 @@
1
+ import * as THREE from "three";
2
+ import { GLSLHParser } from "./parser.js";
3
+
4
+ export class GLSLHLoader {
5
+ constructor(parserInstance) {
6
+ this.parser = parserInstance || new GLSLHParser();
7
+ }
8
+
9
+ async loadShaderModule(filePath) {
10
+ const rawSource = await this.parser.fileResolver(filePath);
11
+ // Pass filePath here so the parser knows the starting directory!
12
+ const parsed = await this.parser.parse(rawSource, filePath);
13
+ const threeUniforms = this.buildThreeUniforms(parsed.uniforms);
14
+
15
+ return {
16
+ source: parsed.source,
17
+ uniforms: threeUniforms,
18
+
19
+ createMaterial(vertexShaderSource, extraOptions = {}) {
20
+ return new THREE.ShaderMaterial({
21
+ vertexShader: vertexShaderSource,
22
+ fragmentShader: parsed.source,
23
+ uniforms: threeUniforms,
24
+ ...extraOptions,
25
+ });
26
+ },
27
+ };
28
+ }
29
+
30
+ buildThreeUniforms(rawDefs) {
31
+ const threeUniforms = {};
32
+
33
+ for (const [key, def] of Object.entries(rawDefs)) {
34
+ let val = def.value;
35
+
36
+ switch (def.type) {
37
+ case "c":
38
+ val = Array.isArray(val)
39
+ ? new THREE.Color(...val)
40
+ : new THREE.Color(val);
41
+ break;
42
+ case "v2":
43
+ val = Array.isArray(val) ? new THREE.Vector2(...val) : val;
44
+ break;
45
+ case "v3":
46
+ val = Array.isArray(val) ? new THREE.Vector3(...val) : val;
47
+ break;
48
+ case "v4":
49
+ val = Array.isArray(val) ? new THREE.Vector4(...val) : val;
50
+ break;
51
+ default:
52
+ val = val;
53
+ break;
54
+ }
55
+
56
+ threeUniforms[key] = { value: val };
57
+ }
58
+
59
+ return threeUniforms;
60
+ }
61
+ }
package/src/parser.js ADDED
@@ -0,0 +1,131 @@
1
+ export class GLSLHParser {
2
+ constructor(fileResolver) {
3
+ this.fileResolver =
4
+ fileResolver ||
5
+ (async (path) => {
6
+ const res = await fetch(path);
7
+ if (!res.ok)
8
+ throw new Error(
9
+ `Could not load shader file: ${path} (Status: ${res.status})`,
10
+ );
11
+ return await res.text();
12
+ });
13
+ this.includeCache = new Map();
14
+
15
+ this.allowedExtensions = [
16
+ ".glsl",
17
+ ".glslh",
18
+ ".frag",
19
+ ".vert",
20
+ ".frag.glsl",
21
+ ".vert.glsl",
22
+ ];
23
+ }
24
+
25
+ validateExtension(filePath) {
26
+ const lowerPath = filePath.toLowerCase();
27
+ const isValid = this.allowedExtensions.some((ext) =>
28
+ lowerPath.endsWith(ext),
29
+ );
30
+ if (!isValid) {
31
+ throw new Error(
32
+ `GLSLH Security Error: Unsupported file extension for include -> '${filePath}'. Allowed extensions: ${this.allowedExtensions.join(", ")}`,
33
+ );
34
+ }
35
+ }
36
+
37
+ resolvePath(parentPath, relativePath) {
38
+ if (!parentPath) return relativePath;
39
+
40
+ const lastSlash = parentPath.lastIndexOf("/");
41
+ if (lastSlash === -1) return relativePath;
42
+ const parentDir = parentPath.substring(0, lastSlash);
43
+
44
+ const cleanRelative = relativePath.startsWith("./")
45
+ ? relativePath.substring(2)
46
+ : relativePath;
47
+
48
+ return `${parentDir}/${cleanRelative}`;
49
+ }
50
+
51
+ async parse(sourceCode, currentFilePath = "") {
52
+ return await this._parseRecursive(sourceCode, currentFilePath, new Set());
53
+ }
54
+
55
+ async _parseRecursive(sourceCode, currentFilePath, includedFiles) {
56
+ let processed = sourceCode;
57
+ const uniformRegex = /\/\*\s*@uniforms([\s\S]*?)\*\//;
58
+ const match = processed.match(uniformRegex);
59
+ let uniformsDefinition = {};
60
+
61
+ if (match) {
62
+ try {
63
+ uniformsDefinition = JSON.parse(match[1].trim());
64
+ processed = processed.replace(uniformRegex, "");
65
+ } catch (e) {
66
+ console.error(
67
+ "GLSLH Parser Error: Invalid JSON format inside @uniforms block.",
68
+ e,
69
+ );
70
+ }
71
+ }
72
+
73
+ const includeRegex = /^#include\s+["<](.+)[">]/gm;
74
+ let includeMatch;
75
+ let includes = [];
76
+
77
+ while ((includeMatch = includeRegex.exec(processed)) !== null) {
78
+ includes.push({ fullMatch: includeMatch[0], path: includeMatch[1] });
79
+ }
80
+
81
+ for (const inc of includes) {
82
+ const resolvedPath = this.resolvePath(currentFilePath, inc.path);
83
+
84
+ this.validateExtension(resolvedPath);
85
+
86
+ if (includedFiles.has(resolvedPath)) {
87
+ processed = processed.replace(
88
+ inc.fullMatch,
89
+ `// Circular include prevented: ${resolvedPath}`,
90
+ );
91
+ continue;
92
+ }
93
+ includedFiles.add(resolvedPath);
94
+
95
+ let subSource = this.includeCache.get(resolvedPath);
96
+ if (!subSource) {
97
+ let rawSubText = await this.fileResolver(resolvedPath);
98
+ const parsedSub = await this._parseRecursive(
99
+ rawSubText,
100
+ resolvedPath,
101
+ includedFiles,
102
+ );
103
+ subSource = parsedSub.source;
104
+ this.includeCache.set(resolvedPath, subSource);
105
+ }
106
+
107
+ processed = processed.replace(
108
+ inc.fullMatch,
109
+ `\n// --- BEGIN INCLUDE: ${resolvedPath} ---\n${subSource}\n// --- END INCLUDE: ${resolvedPath} ---\n`,
110
+ );
111
+ }
112
+
113
+ processed = processed.replace(
114
+ /Math\.([A-Z0-9_]+)/g,
115
+ (match, constantName) => {
116
+ if (constantName === "TAU") return (Math.PI * 2).toString();
117
+ if (typeof Math[constantName] === "number")
118
+ return Math[constantName].toString();
119
+ console.warn(
120
+ `GLSLH Warning: Unknown Math constant 'Math.${constantName}'`,
121
+ );
122
+ return match;
123
+ },
124
+ );
125
+
126
+ return {
127
+ source: processed,
128
+ uniforms: uniformsDefinition,
129
+ };
130
+ }
131
+ }