{"id":19848224,"url":"https://github.com/starlederer/mwgsl","last_synced_at":"2026-03-19T12:02:37.328Z","repository":{"id":53927454,"uuid":"521931825","full_name":"StarLederer/mwgsl","owner":"StarLederer","description":"Modular WGSL. A superset of WGSL that helps you build DRY statically-analyzed shader programs.","archived":false,"fork":false,"pushed_at":"2022-08-13T14:09:03.000Z","size":77,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-11T12:46:43.147Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/StarLederer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-08-06T11:46:57.000Z","updated_at":"2023-10-11T10:10:42.000Z","dependencies_parsed_at":"2022-08-13T04:40:22.368Z","dependency_job_id":null,"html_url":"https://github.com/StarLederer/mwgsl","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StarLederer%2Fmwgsl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StarLederer%2Fmwgsl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StarLederer%2Fmwgsl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/StarLederer%2Fmwgsl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/StarLederer","download_url":"https://codeload.github.com/StarLederer/mwgsl/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241226812,"owners_count":19930487,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-12T13:16:36.995Z","updated_at":"2025-10-22T10:57:11.850Z","avatar_url":"https://github.com/StarLederer.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# MWGSL\n\nModular WGSL. A superset of WGSL that allows developers to break the code up and share it between modules, as well as adds new attributes such as #[env] and #[cfg] that help build DRY statically-analyzed shaders with compile-time superpowers. While in theory this language can be compiled natively, it is mainly designed to be bundled and transpiled into vanilla WGSL.\n\n*Note: This document is works in progress.*\n\n*Note: This document uses Javascript syntax highlighting which is, of course, incorrect, but works better than no highlighting at all.*\n\n## 1 Motivation\n\nThis language is inspired by commonly used C-like shader preprocessors and by the mistakes those preproocessors make. MWGSL is intended to achieve the same end result and replace C-like preprocessors but with a better DX and a modern, robust and Rust-like approach. This language also learns from the operation of ECMAScript bundlers such as [ESBuild](https://esbuild.github.io/) since they solve an almost identical problem, as well as some of the [12 factors](https://12factor.net).\n\n### 1.1 Why do I want MWGSL over a C-like preprocessor\n\n* **Named imports instead of file inclusion.** This means you have the ability to filter and alias what you import and avoid unwanted and unexpected code insertions into your programs.\n* **Module isolation.** In [12-factor](https://12factor.net/dependencies) manner, dependencies of imported objects are isolated in their original module scope and do not conflict with other imported or user defined obects making it effortless to name and define things in your code.\n* **Principaled resource import.** MWGSL forbids redeclaration and double import of resource variables (@group(n) @binding(n)) and forces module develoeprs to export resource variables they use to prevent them from being blocked off from you.\n* **Environment variables.** Defines are unpredictable and prone to unexpected collisions due to their global compile-time nature, that is why MWGSL uses environment variables instead. In [12-factor](https://12factor.net/config) manner, shaders cannot define environemnt variables but they must declare dependence on them. This prevents any unexpected compile time changes or errors when you write code.\n* **Config attribute.** Like #ifdef, MWGSL has a #[cfg] attribute that can mark imports, declarations, struct entries and scopes as optional. Unlike #ifdef, however, #[cfg] takes advantage of existing syntax to make writing invalid code impossible. As a bonus, optional scopes behave closer to an actual WGSL if statements by scoping tokens together.\n* **Including arbitrary/invaid code is impossible.** Horrible code practices such as starting a function in one #include and finishing it in another are syntactically impossible. \n* **Rust inspired syntax** (as opposed to C; for a more civilised age).\n\n### 1.2 Why do I want a shader preprocessor at all\n\n* **Maintainability.** Although shader programs tend to be short, they sometimes span across over a thousand lines of code and cover many isolated mechanisms. The community demonstrates (e.g. [Bevy](https://github.com/bevyengine/bevy/tree/main/crates/bevy_pbr/src/render)) that maintaining extensive shaders like these requires breaking them up into multipe files.\n* **Reusability.** Most game engines include PBR rendering systems, however, it is often required to modify or add to the engine's shaders to achieve certain art directions. Many engines (e.g. [Unity](https://unity.com/features/shader-graph), [Bevy](https://docs.rs/bevy/0.8.0/bevy/pbr/trait.Material.html)) offer mechanisms to reuse built-in logic in user written shaders.\n* **Shader variants.** Branching logic on the GPU is limited ([Bolas, 2016](https://stackoverflow.com/a/37837060)), as a result, compile-time static if statements are often used to generate different shader varants.\n\n## 2 MWGSL features\n\nThis section describes proposed syntactical additions to WGSL.\n\n### 2.1 import\n\nBrings named exports into the shader module.\n\n```js\nimport { EXPORT_NAME } from \"file/path.mwgsl\";\nimport { EXPORT_NAME_1, EXPORT_NAME_2 } from \"file/path.mwgsl\";\n```\n\n### 2.2 export\n\nIndicates to other modules that listed exports can be imported.\n\n```js\nexport { EXPORT_NAME };\n```\n\n### 2.3 Sharable objects\n\nThis section describes WGSL objects that can be shared between modules.\n\n#### 2.3.1 Functions\n\nWGSL functions can be exported.\n\n```js\n// library.mwgsl\n\nfunction bar(a: f32) -\u003e f32 {\n    return b + 1.0;\n}\n\nexport { foo };\n```\n\nDependencies of exported objects do not need to be exported separately.\n\n```js\n// library.mwgsl\n\nfunction bar(a: f32) -\u003e f32 {\n    return b + 1.0;\n}\n\nfunction foo(a: f32) -\u003e f32 {\n    return bar(a + 1.0);\n}\n\nexport { foo }; // MWGSL is aware of bar without it being exported or imported itself. Shall foo be imported into a module where another bar exists, the bar from module-scope of foo is used. No name conflicts occur.\n```\n\nExported functions can depend on varialbes too.\n\n```js\n// library.mwgsl\n\n@group(0) @biding(0)\nvar\u003cuniform\u003e b: f32;\n\nfunction foo(a: f32) -\u003e f32 {\n    return a + b;\n}\n\nexport { b /* b must be exported, this is explained in the section about variables */, foo };\n```\n\n#### 2.3.2 Variables\n\nModule-scope variables can be exported.\n\n```js\n// library.mwgsl\n\n@group(0) @biding(0)\nvar\u003cuniform\u003e b: f32;\n\nexport { b };\n```\n\n```js\n// shader.mwgsl\n\nimport { b } from \"module.mwgsl\";\n\n@fragment\nfn fragment() -\u003e @location(0) vec4\u003cf32\u003e {\n    var color = vec4\u003cf32\u003e(b);\n    return color;\n}\n```\n\n*Warning: A module scope variable with a @group or a @binding attribute must be exported to prevent blocking the binding in importing modules.*\n\n*Note: if MWGSL code is transpiled to WGSL there is a chance a variable can be renamed to resolve name collisions. It is advised that compilers do not rename variables that have @group and @binding attributes*\n\n*TODO: Describe a new attribute for preserving variable names during transpilation to vanilla WGSL.*\n\n##### 2.3.2.1 Resource variable uniqueness\n\nObjects that are or depend on different resource variables with same @group and @binfding attributes cannot be decalred or imported into the same module. Whether the resoruce variable is the same is determined based on whether it comes from the same module.\n\n###### 2.3.2.1.1 Example #1\n\n```js\n// shader.mwgsl\n\nimport { a /* a is @group(0) @binding(0) */ } from \"module.mwgsl\";\n\n@group(0) @biding(0)\nvar\u003cuniform\u003e b: f32; // Illegal because a variable with the same @group and @binding is imported\n```\n\nFix: Do not declare variables with the same @group(0) and @binding(0) atributes\n\n###### 2.3.2.1.2 Example #2\n\n```js\n// library.mwgsl\n\n@group(0) @biding(0)\nvar\u003cuniform\u003e b: f32;\n\nfunction foo(a: f32) -\u003e f32 {\n    return a + b;\n}\n\nexport { b /* b must be exported */, foo };\n```\n\n```js\n// shader.mwgsl\n\nimport { foo } from \"library.mwgsl\";\n\n@group(0) @biding(0)\nvar\u003cuniform\u003e a: f32; // Illegal because foo depends on a variable with the same @group and @binding attributes\n```\n\nFix: Import b instead and optinally alias it.\n\n```js\n// shader.mwgsl\n\nimport { foo, b as a } from \"library.mwgsl\";\n```\n\n###### 2.3.2.1.3 Example #3\n\n```js\n// library_1.mwgsl\n\n@group(0) @biding(0)\nvar\u003cuniform\u003e b: f32;\n\nfunction foo(a: f32) -\u003e f32 {\n    return a + b;\n}\n\nexport { b, foo };\n```\n\n```js\n// library_2.mwgsl\n\n@group(0) @biding(0)\nvar\u003cuniform\u003e b: f32;\n\nfunction bar(a: f32) -\u003e f32 {\n    return b + a;\n}\n\nexport { b, bar };\n```\n\n```js\n// shader.mwgsl\n\nimport { foo } from \"library_1.mwgsl\";\nimport { bar } from \"library_2.mwgsl\"; // Illegal because both foo and bar depend on variables with the same @group and @binding attributes\n```\n\nFix as a user: Choose between bar or foo. They are conflicting imports\n\nFix as a library developer: Split the uniform into a separate module and import it in library_1 and library_2\n\n```js\n// library_uniforms.mwgsl\n\n@group(0) @biding(0)\nvar\u003cuniform\u003e b: f32;\n\nexport { b };\n```\n\n```js\n// library_1.mwgsl\n\nimport { b } from \"library_uniforms.mwgsl\";\n\nfunction foo(a: f32) -\u003e f32 {\n    return a + b;\n}\n\nexport { foo }\n```\n\n```js\n// library_2.mwgsl\n\nimport { b } from \"library_uniforms.mwgsl\";\n\nfunction bar(a: f32) -\u003e f32 {\n    return b + a;\n}\n\nexport { bar }\n```\n\n```js\n// shader.mwgsl\n\nimport { b } from \"library_uniforms.mwgsl\"; // This line is optional if variable with @group(0) and @binding(0) is needed\nimport { foo } from \"library_1.mwgsl\";\nimport { bar } from \"library_2.mwgsl\";\n```\n\n#### 2.3.3 Structs\n\nStructs can be exported.\n\n```js\n// library.mwgsl\n\nstruct VertexOutput {\n    @location(0) world_position: vec4\u003cf32\u003e,\n    @location(1) world_normal: vec3\u003cf32\u003e,\n    @location(2) uv: vec2\u003cf32\u003e,\n}\n\nexport { VertexOutput };\n```\n\nImported structs can be used directly.\n\n```js\n// shader.mwgsl\n\nimport { VertexOutput } from \"module.mwgsl\";\n\n@vertex\nfn vertex(\n    @location(0) vertex_position: vec3\u003cf32\u003e,\n    @location(1) vertex_uv: vec2\u003cf32\u003e,\n) -\u003e VertexOutput {\n    var out: VertexOutput;\n    out.uv = vertex_uv;\n    out.position = view.view_proj * vec4\u003cf32\u003e(vertex_position, 1.0);\n    return out;\n}\n\n@fragment\nfn fragment(in: VertexOutput) -\u003e @location(0) vec4\u003cf32\u003e {\n    var color = vec4\u003cf32\u003e(0.0);\n    return color;\n}\n```\n\nAlternatively, structs can be spread into other structs.\n\n```js\n// shader.mwgsl\n\nimport { VertexOutput } from \"module.mwgsl\";\n\nstruct MyVertexOutput {\n    @builtin(position) clip_position: vec4\u003cf32\u003e,\n    ...VertexOutput,\n}\n```\n\n\n### 2.4 Environment variables\n\n#### 2.4.1 #[env]\n\nCompile-time environemnt variables can be declared as dependencies of structs and functions.\n\n```js\n// library.mwgsl\n\n#[env(COLORED: bool, \"Toggles usage of vertex colors\")]\nstruct VertexInput = {\n    // Compile time attributes can use TOGGLE here \n}\n\n#[env(TOGGLE: bool, \"Toggles optional behaviour\")]\nfn foo() {\n    // Compile time attributes can use TOGGLE here \n}\n\nexport { foo, VertexOutput };\n```\n\nEnvironment dependencies can be accessed by compile-time attributes by the variable's identifier.\n\n```js\n// shader.mwgsl\n\nimport { VertexOutput } from \"library.mwgsl\";\n\n@group(1) @binding(0)\nvar texture: texture_2d\u003cf32\u003e;\n@group(1) @binding(1)\nvar sampler: sampler;\n\n@fragment\nfn fragment(in: VertexOutput) -\u003e @location(0) vec4\u003cf32\u003e {\n    var color = textureSample(texture, sampler, in.uv);\n    #[cfg(VertexOutput.COLORED)] { // Marks scopes as optional. See next section\n        color = in.color * color;\n    }\n    return color;\n}\n```\n\nEnvironment variables can be aliased upon import. This can be used to resolve name conflicts when importing objects.\n\n```js\n// library_1.mwgsl\n\n#[env(COLORED: bool, \"Option that means something\")]\nstruct Structure1 = {\n    // ...\n}\n```\n\n```js\n// library_2.mwgsl\n\n#[env(COLORED: bool, \"Option that means something different\")]\nstruct Structure2 = {\n    // ...\n}\n```\n\n```js\n// shader.mwgsl\n\n#[env_alias(COLORED, VERTEX_COLORS)]\nimport { Structure1 } from \"library_1.mwgsl\";\n#[env_alias(COLORED, TINT)]\nimport { Structure2 } from \"library_2.mwgsl\";\n\nfn foo(one: Structure1, other: Structure2) {\n    // foo depends on VERTEX_COLORS and TINT\n    return color;\n}\n```\n\nMWGSL compilers shall require the user to define environment variables found in the entry file and its dependencies.\n\n```rs\nuse mwgsl::compiler::compile;\nuse std::collections::HashMap;\n\nfn main() {\n    let mut environemnt = HashMap::new();\n    environemnt.insert(\n        \"COLORED\".to_string(),\n        true.to_string(),\n    );\n    compile(\"path/to/shader.mwgsl\", Some(environemnt)).unwrap(); // Shall succeed\n}\n```\n\n```rs\nuse mwgsl::compiler::compile;\n\nfn main() {\n    compile(\"path/to/shader.mwgsl\", None).unwrap(); // Shall fail with a message similar to \"Could not compile /absolute/path/to/shader.mwgsl. VERTEX_COLORS and TINT variables were required by the file but not found in the environment. Please pass a HashMap with the listed environemnt variables to the compile function.\"\n}\n```\n\n#### 2.4.2 #[cfg]\n\nSimilarly to the cfg attibute in Rust, this attribute can mark objects to be ignored. cfg is executed at compile time and operates only on environment variables.\n\n*Bikeshedding material: Should MWGSL @ syntax be used instead?*\n\n*Note: Environment variables are set on the compiler programmatically and not within MWGSL code.*\n\n*Note: While there can in theory be a native MWGSL compiler, this is mainly meant for MWGSL to WGSL transpiers.*\n\n##### 2.4.1 #[cfg] import\n\nImports can be made optional.\n\n```js\n#[cfg(USE_FOO)]\nimport { foo } from \"library.mwgsl\";\n#[@cfg(!USE_FOO)]\nimport { bar as foo } from \"library.mwgsl\";\n```\n\n##### 2.4.2 #[cfg] struct\n\nStructs and can be declared optionally.\n\n```js\n#[cfg(USE_MODULE_OUTPUT_STRUCT)]\nimport { VertexOutput as ModuleVO } from \"module.mwgsl\";\n\n#[cfg(USE_MODULE_OUTPUT_STRUCT)]\nstruct VertexOutput {\n    @builtin(position) clip_position: vec4\u003cf32\u003e,\n    ...ModuleVO,\n}\n\n#[cfg(!USE_MODULE_OUTPUT_STRUCT)]\nstruct VertexOutput {\n    @builtin(position) clip_position: vec4\u003cf32\u003e,\n    @location(0) world_position: vec4\u003cf32\u003e,\n    @location(1) world_normal: vec3\u003cf32\u003e,\n    @location(2) uv: vec2\u003cf32\u003e,\n}\n```\n\nStruct fileds can be added optonally.\n\n```js\nstruct VertexOutput {\n    @location(0) world_position: vec4\u003cf32\u003e,\n    @location(1) world_normal: vec3\u003cf32\u003e,\n    @location(2) uv: vec2\u003cf32\u003e,\n    #[cfg(VERTEX_TANGENTS)]\n    @location(3) world_tangent: vec4\u003cf32\u003e,\n    #[cfg(VERTEX_COLORS)]\n    @location(4) color: vec4\u003cf32\u003e,\n}\n```\n\n##### 2.4.2 #[cfg] function parameter\n\nSimilarly to struct fields, function parameters can be marked as optional.\n\n```js\nfn foo(\n    @location(0) world_position: vec4\u003cf32\u003e,\n    @location(1) world_normal: vec3\u003cf32\u003e,\n    @location(2) uv: vec2\u003cf32\u003e,\n    #[cfg(VERTEX_TANGENTS)]\n    @location(3) world_tangent: vec4\u003cf32\u003e,\n    #[cfg(VERTEX_COLORS)]\n    @location(4) color: vec4\u003cf32\u003e,\n) {}\n```\n\n##### 2.4.4 #[cfg] scope\n\nScopes can be made optional.\n\n*Note: This acts as compile time if statetent*\n\n```js\n#[env(COLORED: bool)]\n@fragment\nfn fragment(in: VertexOutput) -\u003e @location(0) vec4\u003cf32\u003e {\n    var color = textureSample(texture, sampler, in.uv);\n    #[cfg(COLORED)] {\n        color = in.color * color;\n    } // This scope is ignored unless COLORED is present in compilation enviroment\n    return color;\n}\n\nexport { fragment };\n```\n\n## 3 Experimental features\n\nThis section describes controversial and experimental features that might be poorly designed or there might be opposition for. These features are to be migrated or removed after debates are complete.\n\n### 3.1 Optional/named parameters\n\nFunction parameters can be made optional if marked with \"?\".\n\n```js\nfn foo(a: f32, b?: f32, c?: f32) {}\n\nfn bar() {\n    foo(0.0); // Legal\n    foo(0.0, 0.0); // Legal\n    foo(0.0, 0.0, 0.0); // Legal\n}\n```\n\nTo omit arbitrary parameters they can be passed by names.\n\n```js\nfn foo(a: f32, b?: u8, c?: f32) {}\n\nfn bar() {\n    foo(0.0, c: 0.0); // Legal\n    foo(a: 0.0, b: 0); // Legal\n    foo(a: 0.0, b: 0, c: 0.0); // Legal\n}\n```\n\nOptional parameters can only be used in scopes with a #[has] attribute.\n\n```js\nfn add_mul(a: f32, add?: f32, multiply?: f32) -\u003e f32 {\n    var res: f32 = a;\n\n    #[has(add)] {\n        res = res + add;\n    }\n\n    #[has(multiply)] {\n        res = res * multiply;\n    }\n\n    return res;\n}\n```\n\n*Warning: Entry points such as @vertex and @fragment cannot have optional parameters.*\n\n*Note: This feature can cause a lot of variants if transpiled to WGSL. It is strongly recommended that developer tools provide insight into how many variants need to be generated in transpilation.*\n\n### 3.2 The \"environment variable container (EVC)\" pattern\n\nDependence on environment variables is inherited, developers might therefore use the following pattern to group varaibles together and reuse them across modules.\n\n```js\n// pbr_options.mwgsl\n\n// Code repetition\n#[env(COLORED: bool, \"Toggles usage of vertex colors\")]\n#[env(IRIDESCENT: bool, \"Toggles usage of iridescence\")]\n#[env(ANISOTROPIC: bool, \"Toggles usage of anisotropy\")]\n#[env(EMISSIVE: bool, \"Toggles usage of emissive colors\")]\nstruct PBROptions {}\n\nfn use_pbr_options(options?: PBROptions) {}\n\nexport { PBROptions, use_pbr_options };\n```\n\n```js\n// pbr_lib.mwgsl\n\nimport { PBROptions, use_pbr_options } from \"pbr_options.mwgsl\";\n\nstruct VertexInput = {\n    ...PBROptions, // spreads nothing but gives access to PBROptions environment dependencies.\n    #[cfg(PBROptions.COLORED)]\n    @location(0) color: vec4\u003cf32\u003e,\n    // ...\n}\n\nfn calculate_pbr_lighting(/* ... */) -\u003e vec4\u003cf32\u003e {\n    use_pbr_options(); // Does nothing but gives access to use_pbr_options environment dependencies.\n\n    var color = vec4\u003cf32\u003e(1.0);\n\n    // ...\n\n    #[cfg(usePBROptions.COLORED)] {\n        // ...\n    }\n\n    #[cfg(usePBROptions.IRIDESCENT)] {\n        // ...\n    }\n\n    #[cfg(usePBROptions.ANISOTROPIC)] {\n        // ...\n    }\n\n    #[cfg(usePBROptions.EMISSIVE)] {\n        // ...\n    }\n\n    // ...\n\n    return color;\n}\n\nexport { VertexInput, calculate_pbr_lighting };\n```\n\nUnlike other experimental features, the EVC pattern is almost possible with the existing syntax (requires optional variables), however, it is debatable whether it should be allowed or measures should be taken to forbid it. Or perhaps the opposite, maybe it should gain first-class spport?\n\n### 3.3 #[cfg] declarations\n\nAny definition can be marked as optional.\n\n```js\n#[cfg(ZERO_IS_FLOAT)]\n@group(0) @biding(0)\nvar\u003cuniform\u003e a: f32;\n#[cfg(!ZERO_IS_FLOAT)]\n@group(0) @biding(0)\nvar\u003cuniform\u003e a: i32;\n```\n\n```js\n#[cfg(FOO_IS_USED)]\nfn foo() {}\n```\n\nThis feature does not add any value. Definitions can just be exported optionally or elliminated at compile time automatically.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstarlederer%2Fmwgsl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstarlederer%2Fmwgsl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstarlederer%2Fmwgsl/lists"}