├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── clippy.toml ├── examples ├── bevy_pbr_wgsl │ ├── clustered_forward.wgsl │ ├── depth.wgsl │ ├── mesh.wgsl │ ├── mesh_bindings.wgsl │ ├── mesh_functions.wgsl │ ├── mesh_types.wgsl │ ├── mesh_vertex_output.wgsl │ ├── mesh_view_bindings.wgsl │ ├── mesh_view_types.wgsl │ ├── output_VERTEX_UVS.wgsl │ ├── pbr.wgsl │ ├── pbr_bindings.wgsl │ ├── pbr_functions.wgsl │ ├── pbr_lighting.wgsl │ ├── pbr_types.wgsl │ ├── shadows.wgsl │ ├── skinning.wgsl │ ├── utils.wgsl │ └── wireframe.wgsl └── pbr_compose_test.rs └── src ├── compose ├── comment_strip_iter.rs ├── error.rs ├── mod.rs ├── parse_imports.rs ├── preprocess.rs ├── test.rs ├── tests │ ├── add_imports │ │ ├── overridable.wgsl │ │ ├── plugin.wgsl │ │ └── top.wgsl │ ├── atomics │ │ ├── mod.wgsl │ │ └── top.wgsl │ ├── bevy_path_imports │ │ └── skill.wgsl │ ├── big_shaderdefs │ │ ├── mod.wgsl │ │ └── top.wgsl │ ├── call_entrypoint │ │ ├── include.wgsl │ │ └── top.wgsl │ ├── compute_test.wgsl │ ├── conditional_import │ │ ├── mod_a.wgsl │ │ ├── mod_b.wgsl │ │ └── top.wgsl │ ├── conditional_import_fail │ │ ├── middle.wgsl │ │ ├── mod_a_b.wgsl │ │ ├── top.wgsl │ │ └── top_nested.wgsl │ ├── const_in_decl │ │ ├── bind.wgsl │ │ ├── consts.wgsl │ │ └── top.wgsl │ ├── diagnostic_filters │ │ ├── filters.wgsl │ │ └── top.wgsl │ ├── dup_import │ │ ├── a.wgsl │ │ ├── all.wgsl │ │ ├── b.wgsl │ │ ├── consts.wgsl │ │ └── top.wgsl │ ├── dup_struct_import │ │ ├── a.wgsl │ │ ├── b.wgsl │ │ ├── struct.wgsl │ │ └── top.wgsl │ ├── effective_defs │ │ ├── mod.wgsl │ │ └── top.wgsl │ ├── error_test │ │ ├── include.wgsl │ │ ├── wgsl_parse_err.wgsl │ │ ├── wgsl_parse_wrap.wgsl │ │ ├── wgsl_valid_err.wgsl │ │ └── wgsl_valid_wrap.wgsl │ ├── expected │ │ ├── additional_import.txt │ │ ├── atomics.txt │ │ ├── bad_identifiers.txt │ │ ├── big_shaderdefs.txt │ │ ├── conditional_import_a.txt │ │ ├── conditional_import_b.txt │ │ ├── conditional_missing_import.txt │ │ ├── conditional_missing_import_nested.txt │ │ ├── diagnostic_filters.txt │ │ ├── dup_import.txt │ │ ├── dup_struct_import.txt │ │ ├── err_parse.txt │ │ ├── err_validation_1.txt │ │ ├── err_validation_2.txt │ │ ├── glsl_call_wgsl.txt │ │ ├── glsl_const_import.txt │ │ ├── glsl_wgsl_const_import.txt │ │ ├── import_in_decl.txt │ │ ├── invalid_override_base.txt │ │ ├── item_import_test.txt │ │ ├── item_sub_point.txt │ │ ├── missing_import.txt │ │ ├── simple_compose.txt │ │ ├── test_quoted_import_dup_name.txt │ │ ├── use_shared_global.txt │ │ ├── wgsl_call_entrypoint.txt │ │ ├── wgsl_call_glsl.txt │ │ └── wgsl_glsl_const_import.txt │ ├── glsl │ │ ├── basic.glsl │ │ ├── module.glsl │ │ ├── module.wgsl │ │ ├── top.glsl │ │ └── top.wgsl │ ├── glsl_const_import │ │ ├── consts.glsl │ │ ├── consts.wgsl │ │ ├── top.glsl │ │ └── top.wgsl │ ├── invalid_identifiers │ │ ├── const.wgsl │ │ ├── fn.wgsl │ │ ├── global.wgsl │ │ ├── struct.wgsl │ │ ├── struct_member.wgsl │ │ ├── top_invalid.wgsl │ │ └── top_valid.wgsl │ ├── item_import │ │ ├── consts.wgsl │ │ └── top.wgsl │ ├── item_sub_point │ │ ├── mod.wgsl │ │ └── top.wgsl │ ├── modf │ │ ├── mod.wgsl │ │ └── top.wgsl │ ├── overrides │ │ ├── middle.wgsl │ │ ├── mod.wgsl │ │ ├── top.wgsl │ │ ├── top_invalid.wgsl │ │ └── top_with_middle.wgsl │ ├── quoted_dup │ │ ├── mod.wgsl │ │ └── top.wgsl │ ├── raycast │ │ ├── mod.wgsl │ │ └── top.wgsl │ ├── rusty_imports │ │ ├── mod_a_b_c.wgsl │ │ ├── mod_a_x.wgsl │ │ └── top.wgsl │ ├── simple │ │ ├── inc.wgsl │ │ └── top.wgsl │ └── use_shared_global │ │ ├── mod.wgsl │ │ └── top.wgsl └── tokenizer.rs ├── derive.rs ├── lib.rs ├── prune ├── mod.rs ├── test.rs └── tests │ ├── frag_reduced.wgsl │ ├── frag_reduced_2.wgsl │ ├── import.wgsl │ ├── pbr_fn.wgsl │ ├── pbr_reduced.wgsl │ └── test.wgsl └── redirect.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Continuous integration 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: dtolnay/rust-toolchain@stable 12 | - name: Check 13 | run: | 14 | cargo check 15 | cargo check --no-default-features 16 | 17 | check-wasm: 18 | name: Check (Wasm) 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v2 22 | - uses: dtolnay/rust-toolchain@stable 23 | with: 24 | target: wasm32-unknown-unknown 25 | - name: Check wasm 26 | run: | 27 | cargo check --target wasm32-unknown-unknown 28 | cargo check --target wasm32-unknown-unknown --no-default-features 29 | 30 | test-windows: 31 | name: Test Suite (Windows) 32 | runs-on: windows-latest 33 | steps: 34 | - uses: actions/checkout@v2 35 | - uses: dtolnay/rust-toolchain@stable 36 | - name: Test 37 | run: cargo test 38 | 39 | test-ubuntu: 40 | name: Test Suite (Ubuntu) 41 | runs-on: ubuntu-latest 42 | steps: 43 | - uses: actions/checkout@v2 44 | - uses: dtolnay/rust-toolchain@stable 45 | - name: install dependencies 46 | run: | 47 | sudo apt-get update -y -qq 48 | sudo add-apt-repository ppa:kisak/turtle -y 49 | sudo apt-get update 50 | sudo apt install -y xvfb libgl1-mesa-dri libxcb-xfixes0-dev mesa-vulkan-drivers 51 | - name: Test 52 | run: xvfb-run cargo test 53 | 54 | test-macos: 55 | name: Test Suite (MacOS) 56 | runs-on: macos-latest 57 | steps: 58 | - uses: actions/checkout@v2 59 | - uses: dtolnay/rust-toolchain@stable 60 | - name: Test 61 | run: cargo test --no-default-features 62 | 63 | fmt: 64 | name: Rustfmt 65 | runs-on: ubuntu-latest 66 | steps: 67 | - uses: actions/checkout@v2 68 | - uses: dtolnay/rust-toolchain@stable 69 | with: 70 | components: rustfmt 71 | - name: Format 72 | run: cargo fmt --all -- --check 73 | 74 | clippy: 75 | name: Clippy 76 | runs-on: ubuntu-latest 77 | steps: 78 | - uses: actions/checkout@v2 79 | - uses: dtolnay/rust-toolchain@stable 80 | with: 81 | components: clippy 82 | - name: Clippy 83 | run: cargo clippy -- -D warnings 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | bevy_target.jpg 4 | import_example.png 5 | override.png 6 | out.txt 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "naga_oil" 3 | version = "0.17.0" 4 | edition = "2021" 5 | license = "MIT OR Apache-2.0" 6 | description = "a crate for combining and manipulating shaders using naga IR" 7 | repository = "https://github.com/bevyengine/naga_oil/" 8 | readme = "README.md" 9 | rust-version = "1.84" 10 | 11 | [features] 12 | default = ["test_shader", "glsl"] 13 | # enable tests that need a graphical card 14 | test_shader = [] 15 | glsl = ["naga/glsl-in", "naga/glsl-out"] 16 | override_any = [] 17 | prune = [] 18 | allow_deprecated = [] 19 | 20 | [dependencies] 21 | naga = { version = "25", features = ["wgsl-in", "wgsl-out"] } 22 | tracing = "0.1" 23 | regex = "1.8" 24 | regex-syntax = "0.8" 25 | thiserror = "2.0" 26 | codespan-reporting = "0.12" 27 | data-encoding = "2.3.2" 28 | bit-set = "0.8" 29 | rustc-hash = "1.1" 30 | unicode-ident = "1" 31 | once_cell = "1.17.0" 32 | indexmap = "2" 33 | 34 | [dev-dependencies] 35 | wgpu = { version = "25", features = ["naga-ir"] } 36 | futures-lite = "2" 37 | tracing-subscriber = { version = "0.3", features = ["std", "fmt"] } 38 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Naga Organised Integration Library (`naga-oil`) is a crate for combining and manipulating shaders. 2 | 3 | - `compose` presents a modular shader composition framework 4 | - `prune` strips shaders down to required parts 5 | 6 | and probably less useful externally: 7 | - `derive` allows importing of items from multiple shaders into a single shader 8 | - `redirect` modifies a shader by substituting function calls and modifying bindings 9 | 10 | # Compose 11 | 12 | the compose module allows construction of shaders from modules (which are themselves shaders). 13 | 14 | it does this by treating shaders as modules, and 15 | - building each module independently to naga IR 16 | - creating "header" files for each supported language, which are used to build dependent modules/shaders 17 | - making final shaders by combining the shader IR with the IR for imported modules 18 | 19 | for multiple small shaders with large common imports, this can be faster than parsing the full source for each shader, and it allows for constructing shaders in a cleaner modular manner with better scope control. 20 | 21 | ## imports 22 | 23 | shaders can be added to the composer as modules. this makes their types, constants, variables and functions available to modules/shaders that import them. note that importing a module will affect the final shader's global state if the module defines globals variables with bindings. 24 | 25 | modules may include a `#define_import_path` directive that names the module: 26 | 27 | ```wgsl 28 | #define_import_path my_module 29 | 30 | fn my_func() -> f32 { 31 | return 1.0; 32 | } 33 | ``` 34 | 35 | alternatively the module name can be specified as an argument to `Composer::add_composable_module`. 36 | 37 | shaders can then import the module with an `#import` directive (with an optional `as` name) : 38 | 39 | ```wgsl 40 | #import my_module; 41 | #import my_other_module as mod2; 42 | 43 | fn main() -> f32 { 44 | let x = my_module::my_func(); 45 | let y = mod2::my_other_func(); 46 | return x*y; 47 | } 48 | ``` 49 | 50 | or import a comma-separated list of individual items : 51 | 52 | ```wgsl 53 | #import my_module::{my_func, my_const} 54 | 55 | fn main() -> f32 { 56 | return my_func(my_const); 57 | } 58 | ``` 59 | 60 | Some rust-style import syntax is supported, and items can be directly imported using the fully qualified item name : 61 | 62 | ```wgsl 63 | #import my_package::{ 64 | first_module::{item_one as item, item_two}, 65 | second_module::submodule, 66 | } 67 | 68 | fn main() -> f32 { 69 | return item + item_two + submodule::subitem + my_package::third_module::item; 70 | } 71 | ``` 72 | 73 | `module::self` and `module::*` are not currently supported. 74 | 75 | imports can be nested - modules may import other modules, but not recursively. when a new module is added, all its `#import`s must already have been added. 76 | the same module can be imported multiple times by different modules in the import tree. 77 | there is no overlap of namespaces, so the same function names (or type, constant, or variable names) may be used in different modules. 78 | 79 | note: the final shader will include the required dependencies (bindings, globals, consts, other functions) of any imported items that are used, but will not include the rest of the imported module. 80 | 81 | ## overriding functions 82 | 83 | virtual functions can be declared with the `virtual` keyword: 84 | ```glsl 85 | virtual fn point_light(world_position: vec3) -> vec3 { ... } 86 | ``` 87 | virtual functions defined in imported modules can then be overridden using the `override` keyword: 88 | 89 | ```wgsl 90 | #import bevy_pbr::lighting as Lighting 91 | 92 | override fn Lighting::point_light (world_position: vec3) -> vec3 { 93 | let original = Lighting::point_light(world_position); 94 | let quantized = vec3(original * 3.0); 95 | return vec3(quantized) / 3.0; 96 | } 97 | ``` 98 | 99 | overrides must either be declared in the top-level shader, or the module containing the override must be imported as an `additional_import` in a `Composer::add_composable_module` or `Composer::make_naga_module` call. using `#import` to import a module with overrides will not work due to tree-shaking. 100 | 101 | override function definitions cause *all* calls to the original function in the entire shader scope to be replaced by calls to the new function, with the exception of calls within the override function itself. 102 | 103 | the function signature of the override must match the base function. 104 | 105 | overrides can be specified at any point in the final shader's import tree. 106 | 107 | multiple overrides can be applied to the same function. for example, given : 108 | - a module `a` containing a function `f`, 109 | - a module `b` that imports `a`, and containing an `override a::f` function, 110 | - a module `c` that imports `a` and `b`, and containing an `override a::f` function, 111 | 112 | then `b` and `c` both specify an override for `a::f`. 113 | 114 | the `override fn a::f` declared in module `b` may call to `a::f` within its body. 115 | 116 | the `override fn a::f` declared in module `c` may call to `a::f` within its body, but the call will be redirected to `b::f`. 117 | 118 | any other calls to `a::f` (within modules `a` or `b`, or anywhere else) will end up redirected to `c::f`. 119 | 120 | in this way a chain or stack of overrides can be applied. 121 | 122 | different overrides of the same function can be specified in different import branches. the final stack will be ordered based on the first occurrence of the override in the import tree (using a depth first search). 123 | 124 | note that imports into a module/shader are processed in order, but are processed before the body of the current shader/module regardless of where they occur in that module, so there is no way to import a module containing an override and inject a call into the override stack prior to that imported override. you can instead create two modules each containing an override and import them into a parent module/shader to order them as required. 125 | 126 | override functions can currently only be defined in wgsl. 127 | 128 | if the `override_any` crate feature is enabled, then the `virtual` keyword is not required for the function being overridden. 129 | 130 | ## languages 131 | 132 | modules can we written in GLSL or WGSL. shaders with entry points can be imported as modules (provided they have a `#define_import_path` directive). entry points are available to call from imported modules either via their name (for WGSL) or via `module::main` (for GLSL). 133 | 134 | final shaders can also be written in GLSL or WGSL. for GLSL users must specify whether the shader is a vertex shader or fragment shader via the ShaderType argument (GLSL compute shaders are not supported). 135 | 136 | ## preprocessing 137 | 138 | when generating a final shader or adding a composable module, a set of `shader_def` string/value pairs must be provided. The value can be a bool (`ShaderDefValue::Bool`), an i32 (`ShaderDefValue::Int`) or a u32 (`ShaderDefValue::UInt`). 139 | 140 | these allow conditional compilation of parts of modules and the final shader. conditional compilation is performed with `#if` / `#ifdef` / `#ifndef`, `#else` and `#endif` preprocessor directives: 141 | 142 | ```wgsl 143 | fn get_number() -> f32 { 144 | #ifdef BIG_NUMBER 145 | return 999.0; 146 | #else 147 | return 0.999; 148 | #endif 149 | } 150 | ``` 151 | the `#ifdef` directive matches when the def name exists in the input binding set (regardless of value). the `#ifndef` directive is the reverse. 152 | 153 | the `#if` directive requires a def name, an operator, and a value for comparison: 154 | - the def name must be a provided `shader_def` name. 155 | - the operator must be one of `==`, `!=`, `>=`, `>`, `<`, `<=` 156 | - the value must be an integer literal if comparing to a `ShaderDefValue::Int` or `ShaderDefValue::Uint`, or `true` or `false` if comparing to a `ShaderDef::Bool`. 157 | 158 | shader defs can also be used in the shader source with `#SHADER_DEF` or `#{SHADER_DEF}`, and will be substituted for their value. 159 | 160 | the preprocessor branching directives (`ifdef`, `ifndef` and `if`) can be prefixed with `#else` to create more complex control flows: 161 | 162 | ```wgsl 163 | fn get_number() -> f32 { 164 | #ifdef BIG_NUMBER 165 | return 999.0; 166 | #else if USER_NUMBER > 1 167 | return f32(#USER_NUMBER) 168 | #else 169 | return 0.999; 170 | #endif 171 | } 172 | ``` 173 | 174 | shader defs can be created or overridden at the start of the top-level shader with the `#define` directive: 175 | ```wgsl 176 | #define USER_NUMBER 42 177 | ``` 178 | the created value will default to `true` if not specified. 179 | 180 | ## error reporting 181 | 182 | codespan reporting for errors is available using the error `emit_to_string` method. this requires validation to be enabled, which is true by default. `Composer::non_validating()` produces a non-validating composer that is not able to give accurate error reporting. 183 | 184 | # prune 185 | 186 | - strips dead code and bindings from shaders based on specified required output. intended to be used for building reduced depth and/or normal shaders from arbitrary vertex/fragment shaders. 187 | 188 | proper docs tbd 189 | 190 | # redirect 191 | 192 | - redirects function calls 193 | - wip: rebinds global bindings 194 | - todo one day: translate between uniform, texture and buffer accesses so shaders written for direct passes can be used in indirect 195 | 196 | proper docs tbd 197 | 198 | # derive 199 | 200 | - builds a single self-contained naga module out of parts of one or more existing modules 201 | 202 | proper docs tbd 203 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | large-error-threshold = 256 2 | enum-variant-size-threshold = 256 3 | -------------------------------------------------------------------------------- /examples/bevy_pbr_wgsl/clustered_forward.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path bevy_pbr::clustered_forward 2 | 3 | #import bevy_pbr::mesh_view_bindings as Bindings 4 | 5 | // NOTE: Keep in sync with bevy_pbr/src/light.rs 6 | fn view_z_to_z_slice(view_z: f32, is_orthographic: bool) -> u32 { 7 | var z_slice: u32 = 0u; 8 | if (is_orthographic) { 9 | // NOTE: view_z is correct in the orthographic case 10 | z_slice = u32(floor((view_z - Bindings::lights.cluster_factors.z) * Bindings::lights.cluster_factors.w)); 11 | } else { 12 | // NOTE: had to use -view_z to make it positive else log(negative) is nan 13 | z_slice = u32(log(-view_z) * Bindings::lights.cluster_factors.z - Bindings::lights.cluster_factors.w + 1.0); 14 | } 15 | // NOTE: We use min as we may limit the far z plane used for clustering to be closeer than 16 | // the furthest thing being drawn. This means that we need to limit to the maximum cluster. 17 | return min(z_slice, Bindings::lights.cluster_dimensions.z - 1u); 18 | } 19 | 20 | fn fragment_cluster_index(frag_coord: vec2, view_z: f32, is_orthographic: bool) -> u32 { 21 | let xy = vec2(floor(frag_coord * Bindings::lights.cluster_factors.xy)); 22 | let z_slice = view_z_to_z_slice(view_z, is_orthographic); 23 | // NOTE: Restricting cluster index to avoid undefined behavior when accessing uniform buffer 24 | // arrays based on the cluster index. 25 | return min( 26 | (xy.y * Bindings::lights.cluster_dimensions.x + xy.x) * Bindings::lights.cluster_dimensions.z + z_slice, 27 | Bindings::lights.cluster_dimensions.w - 1u 28 | ); 29 | } 30 | 31 | // this must match CLUSTER_COUNT_SIZE in light.rs 32 | const CLUSTER_COUNT_SIZE = 9u; 33 | fn unpack_offset_and_counts(cluster_index: u32) -> vec3 { 34 | #ifdef NO_STORAGE_BUFFERS_SUPPORT 35 | let offset_and_counts = Bindings::cluster_offsets_and_counts.data[cluster_index >> 2u][cluster_index & ((1u << 2u) - 1u)]; 36 | // [ 31 .. 18 | 17 .. 9 | 8 .. 0 ] 37 | // [ offset | point light count | spot light count ] 38 | return vec3( 39 | (offset_and_counts >> (CLUSTER_COUNT_SIZE * 2u)) & ((1u << (32u - (CLUSTER_COUNT_SIZE * 2u))) - 1u), 40 | (offset_and_counts >> CLUSTER_COUNT_SIZE) & ((1u << CLUSTER_COUNT_SIZE) - 1u), 41 | offset_and_counts & ((1u << CLUSTER_COUNT_SIZE) - 1u), 42 | ); 43 | #else 44 | return Bindings::cluster_offsets_and_counts.data[cluster_index].xyz; 45 | #endif 46 | } 47 | 48 | fn get_light_id(index: u32) -> u32 { 49 | #ifdef NO_STORAGE_BUFFERS_SUPPORT 50 | // The index is correct but in cluster_light_index_lists we pack 4 u8s into a u32 51 | // This means the index into cluster_light_index_lists is index / 4 52 | let indices = Bindings::cluster_light_index_lists.data[index >> 4u][(index >> 2u) & ((1u << 2u) - 1u)]; 53 | // And index % 4 gives the sub-index of the u8 within the u32 so we shift by 8 * sub-index 54 | return (indices >> (8u * (index & ((1u << 2u) - 1u)))) & ((1u << 8u) - 1u); 55 | #else 56 | return Bindings::cluster_light_index_lists.data[index]; 57 | #endif 58 | } 59 | 60 | fn cluster_debug_visualization( 61 | output_color: vec4, 62 | view_z: f32, 63 | is_orthographic: bool, 64 | offset_and_counts: vec3, 65 | cluster_index: u32, 66 | ) -> vec4 { 67 | // Cluster allocation debug (using 'over' alpha blending) 68 | #ifdef CLUSTERED_FORWARD_DEBUG_Z_SLICES 69 | // NOTE: This debug mode visualises the z-slices 70 | let cluster_overlay_alpha = 0.1; 71 | var z_slice: u32 = view_z_to_z_slice(view_z, is_orthographic); 72 | // A hack to make the colors alternate a bit more 73 | if ((z_slice & 1u) == 1u) { 74 | z_slice = z_slice + Bindings::lights.cluster_dimensions.z / 2u; 75 | } 76 | let slice_color = hsv2rgb(f32(z_slice) / f32(Bindings::lights.cluster_dimensions.z + 1u), 1.0, 0.5); 77 | output_color = vec4( 78 | (1.0 - cluster_overlay_alpha) * output_color.rgb + cluster_overlay_alpha * slice_color, 79 | output_color.a 80 | ); 81 | #endif // CLUSTERED_FORWARD_DEBUG_Z_SLICES 82 | #ifdef CLUSTERED_FORWARD_DEBUG_CLUSTER_LIGHT_COMPLEXITY 83 | // NOTE: This debug mode visualises the number of lights within the cluster that contains 84 | // the fragment. It shows a sort of lighting complexity measure. 85 | let cluster_overlay_alpha = 0.1; 86 | let max_light_complexity_per_cluster = 64.0; 87 | output_color.r = (1.0 - cluster_overlay_alpha) * output_color.r 88 | + cluster_overlay_alpha * smoothStep(0.0, max_light_complexity_per_cluster, f32(offset_and_counts[1] + offset_and_counts[2])); 89 | output_color.g = (1.0 - cluster_overlay_alpha) * output_color.g 90 | + cluster_overlay_alpha * (1.0 - smoothStep(0.0, max_light_complexity_per_cluster, f32(offset_and_counts[1] + offset_and_counts[2]))); 91 | #endif // CLUSTERED_FORWARD_DEBUG_CLUSTER_LIGHT_COMPLEXITY 92 | #ifdef CLUSTERED_FORWARD_DEBUG_CLUSTER_COHERENCY 93 | // NOTE: Visualizes the cluster to which the fragment belongs 94 | let cluster_overlay_alpha = 0.1; 95 | let cluster_color = hsv2rgb(random1D(f32(cluster_index)), 1.0, 0.5); 96 | output_color = vec4( 97 | (1.0 - cluster_overlay_alpha) * output_color.rgb + cluster_overlay_alpha * cluster_color, 98 | output_color.a 99 | ); 100 | #endif // CLUSTERED_FORWARD_DEBUG_CLUSTER_COHERENCY 101 | 102 | return output_color; 103 | } 104 | -------------------------------------------------------------------------------- /examples/bevy_pbr_wgsl/depth.wgsl: -------------------------------------------------------------------------------- 1 | #import bevy_pbr::mesh_view_types 2 | #import bevy_pbr::mesh_types 3 | 4 | @group(0) @binding(0) 5 | var view: View; 6 | 7 | @group(1) @binding(0) 8 | var mesh: Mesh; 9 | 10 | #ifdef SKINNED 11 | @group(1) @binding(1) 12 | var joint_matrices: SkinnedMesh; 13 | #import bevy_pbr::skinning 14 | #endif 15 | 16 | // NOTE: Bindings must come before functions that use them! 17 | #import bevy_pbr::mesh_functions 18 | 19 | struct Vertex { 20 | @location(0) position: vec3, 21 | #ifdef SKINNED 22 | @location(4) joint_indices: vec4, 23 | @location(5) joint_weights: vec4, 24 | #endif 25 | }; 26 | 27 | struct VertexOutput { 28 | @builtin(position) clip_position: vec4, 29 | }; 30 | 31 | @vertex 32 | fn vertex(vertex: Vertex) -> VertexOutput { 33 | #ifdef SKINNED 34 | let model = skin_model(vertex.joint_indices, vertex.joint_weights); 35 | #else 36 | let model = mesh.model; 37 | #endif 38 | 39 | var out: VertexOutput; 40 | out.clip_position = mesh_position_local_to_clip(model, vec4(vertex.position, 1.0)); 41 | return out; 42 | } 43 | -------------------------------------------------------------------------------- /examples/bevy_pbr_wgsl/mesh.wgsl: -------------------------------------------------------------------------------- 1 | #import bevy_pbr::mesh_view_bindings 2 | #import bevy_pbr::mesh_bindings 3 | 4 | // NOTE: Bindings must come before functions that use them! 5 | #import bevy_pbr::mesh_functions 6 | 7 | struct Vertex { 8 | @location(0) position: vec3, 9 | @location(1) normal: vec3, 10 | #ifdef VERTEX_UVS 11 | @location(2) uv: vec2, 12 | #endif 13 | #ifdef VERTEX_TANGENTS 14 | @location(3) tangent: vec4, 15 | #endif 16 | #ifdef VERTEX_COLORS 17 | @location(4) color: vec4, 18 | #endif 19 | #ifdef SKINNED 20 | @location(5) joint_indices: vec4, 21 | @location(6) joint_weights: vec4, 22 | #endif 23 | }; 24 | 25 | struct VertexOutput { 26 | @builtin(position) clip_position: vec4, 27 | #import bevy_pbr::mesh_vertex_output 28 | }; 29 | 30 | @vertex 31 | fn vertex(vertex: Vertex) -> VertexOutput { 32 | var out: VertexOutput; 33 | #ifdef SKINNED 34 | var model = skin_model(vertex.joint_indices, vertex.joint_weights); 35 | out.world_normal = skin_normals(model, vertex.normal); 36 | #else 37 | var model = mesh.model; 38 | out.world_normal = mesh_normal_local_to_world(vertex.normal); 39 | #endif 40 | out.world_position = mesh_position_local_to_world(model, vec4(vertex.position, 1.0)); 41 | #ifdef VERTEX_UVS 42 | out.uv = vertex.uv; 43 | #endif 44 | #ifdef VERTEX_TANGENTS 45 | out.world_tangent = mesh_tangent_local_to_world(model, vertex.tangent); 46 | #endif 47 | #ifdef VERTEX_COLORS 48 | out.color = vertex.color; 49 | #endif 50 | 51 | out.clip_position = mesh_position_world_to_clip(out.world_position); 52 | return out; 53 | } 54 | 55 | struct FragmentInput { 56 | @builtin(front_facing) is_front: bool, 57 | #import bevy_pbr::mesh_vertex_output 58 | }; 59 | 60 | @fragment 61 | fn fragment(in: FragmentInput) -> @location(0) vec4 { 62 | #ifdef VERTEX_COLORS 63 | return in.color; 64 | #else 65 | return vec4(1.0, 0.0, 1.0, 1.0); 66 | #endif 67 | } 68 | -------------------------------------------------------------------------------- /examples/bevy_pbr_wgsl/mesh_bindings.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path bevy_pbr::mesh_bindings 2 | 3 | #import bevy_pbr::mesh_types as Types 4 | 5 | @group(2) @binding(0) 6 | var mesh: Types::Mesh; 7 | -------------------------------------------------------------------------------- /examples/bevy_pbr_wgsl/mesh_functions.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path bevy_pbr::mesh_functions 2 | 3 | fn mesh_position_local_to_world(model: mat4x4, vertex_position: vec4) -> vec4 { 4 | return model * vertex_position; 5 | } 6 | 7 | fn mesh_position_world_to_clip(world_position: vec4) -> vec4 { 8 | return view.view_proj * world_position; 9 | } 10 | 11 | // NOTE: The intermediate world_position assignment is important 12 | // for precision purposes when using the 'equals' depth comparison 13 | // function. 14 | fn mesh_position_local_to_clip(model: mat4x4, vertex_position: vec4) -> vec4 { 15 | let world_position = mesh_position_local_to_world(model, vertex_position); 16 | return mesh_position_world_to_clip(world_position); 17 | } 18 | 19 | fn mesh_normal_local_to_world(vertex_normal: vec3) -> vec3 { 20 | return mat3x3( 21 | mesh.inverse_transpose_model[0].xyz, 22 | mesh.inverse_transpose_model[1].xyz, 23 | mesh.inverse_transpose_model[2].xyz 24 | ) * vertex_normal; 25 | } 26 | 27 | fn mesh_tangent_local_to_world(model: mat4x4, vertex_tangent: vec4) -> vec4 { 28 | return vec4( 29 | mat3x3( 30 | model[0].xyz, 31 | model[1].xyz, 32 | model[2].xyz 33 | ) * vertex_tangent.xyz, 34 | vertex_tangent.w 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /examples/bevy_pbr_wgsl/mesh_types.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path bevy_pbr::mesh_types 2 | 3 | struct Mesh { 4 | model: mat4x4, 5 | inverse_transpose_model: mat4x4, 6 | // 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options. 7 | flags: u32, 8 | }; 9 | 10 | #ifdef SKINNED 11 | struct SkinnedMesh { 12 | data: array, 256u>, 13 | }; 14 | #endif 15 | 16 | const MESH_FLAGS_SHADOW_RECEIVER_BIT: u32 = 1u; 17 | -------------------------------------------------------------------------------- /examples/bevy_pbr_wgsl/mesh_vertex_output.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path bevy_pbr::mesh_vertex_output 2 | 3 | struct MeshVertexOutput { 4 | @location(0) world_position: vec4, 5 | @location(1) world_normal: vec3, 6 | #ifdef VERTEX_UVS 7 | @location(2) uv: vec2, 8 | #endif 9 | #ifdef VERTEX_TANGENTS 10 | @location(3) world_tangent: vec4, 11 | #endif 12 | #ifdef VERTEX_COLORS 13 | @location(4) color: vec4, 14 | #endif 15 | } 16 | -------------------------------------------------------------------------------- /examples/bevy_pbr_wgsl/mesh_view_bindings.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path bevy_pbr::mesh_view_bindings 2 | 3 | #import bevy_pbr::mesh_view_types as Types 4 | 5 | @group(0) @binding(0) 6 | var view: Types::View; 7 | @group(0) @binding(1) 8 | var lights: Types::Lights; 9 | #ifdef NO_ARRAY_TEXTURES_SUPPORT 10 | @group(0) @binding(2) 11 | var point_shadow_textures: texture_depth_cube; 12 | #else 13 | @group(0) @binding(2) 14 | var point_shadow_textures: texture_depth_cube_array; 15 | #endif 16 | @group(0) @binding(3) 17 | var point_shadow_textures_sampler: sampler_comparison; 18 | #ifdef NO_ARRAY_TEXTURES_SUPPORT 19 | @group(0) @binding(4) 20 | var directional_shadow_textures: texture_depth_2d; 21 | #else 22 | @group(0) @binding(4) 23 | var directional_shadow_textures: texture_depth_2d_array; 24 | #endif 25 | @group(0) @binding(5) 26 | var directional_shadow_textures_sampler: sampler_comparison; 27 | 28 | #ifdef NO_STORAGE_BUFFERS_SUPPORT 29 | @group(0) @binding(6) 30 | var point_lights: Types::PointLights; 31 | @group(0) @binding(7) 32 | var cluster_light_index_lists: Types::ClusterLightIndexLists; 33 | @group(0) @binding(8) 34 | var cluster_offsets_and_counts: Types::ClusterOffsetsAndCounts; 35 | #else 36 | @group(0) @binding(6) 37 | var point_lights: Types::PointLights; 38 | @group(0) @binding(7) 39 | var cluster_light_index_lists: Types::ClusterLightIndexLists; 40 | @group(0) @binding(8) 41 | var cluster_offsets_and_counts: Types::ClusterOffsetsAndCounts; 42 | #endif 43 | -------------------------------------------------------------------------------- /examples/bevy_pbr_wgsl/mesh_view_types.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path bevy_pbr::mesh_view_types 2 | 3 | struct View { 4 | view_proj: mat4x4, 5 | inverse_view_proj: mat4x4, 6 | view: mat4x4, 7 | inverse_view: mat4x4, 8 | projection: mat4x4, 9 | inverse_projection: mat4x4, 10 | world_position: vec3, 11 | width: f32, 12 | height: f32, 13 | }; 14 | 15 | struct PointLight { 16 | // For point lights: the lower-right 2x2 values of the projection matrix [2][2] [2][3] [3][2] [3][3] 17 | // For spot lights: the direction (x,z), spot_scale and spot_offset 18 | light_custom_data: vec4, 19 | color_inverse_square_range: vec4, 20 | position_radius: vec4, 21 | // 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options. 22 | flags: u32, 23 | shadow_depth_bias: f32, 24 | shadow_normal_bias: f32, 25 | spot_light_tan_angle: f32, 26 | }; 27 | 28 | const POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u; 29 | const POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE: u32 = 2u; 30 | 31 | struct DirectionalLight { 32 | view_projection: mat4x4, 33 | color: vec4, 34 | direction_to_light: vec3, 35 | // 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options. 36 | flags: u32, 37 | shadow_depth_bias: f32, 38 | shadow_normal_bias: f32, 39 | }; 40 | 41 | const DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u; 42 | 43 | struct Lights { 44 | // NOTE: this array size must be kept in sync with the constants defined bevy_pbr2/src/render/light.rs 45 | directional_lights: array, 46 | ambient_color: vec4, 47 | // x/y/z dimensions and n_clusters in w 48 | cluster_dimensions: vec4, 49 | // xy are vec2(cluster_dimensions.xy) / vec2(view.width, view.height) 50 | // 51 | // For perspective projections: 52 | // z is cluster_dimensions.z / log(far / near) 53 | // w is cluster_dimensions.z * log(near) / log(far / near) 54 | // 55 | // For orthographic projections: 56 | // NOTE: near and far are +ve but -z is infront of the camera 57 | // z is -near 58 | // w is cluster_dimensions.z / (-far - -near) 59 | cluster_factors: vec4, 60 | n_directional_lights: u32, 61 | spot_light_shadowmap_offset: i32, 62 | }; 63 | 64 | #ifdef NO_STORAGE_BUFFERS_SUPPORT 65 | struct PointLights { 66 | data: array, 67 | }; 68 | struct ClusterLightIndexLists { 69 | // each u32 contains 4 u8 indices into the PointLights array 70 | data: array, 1024u>, 71 | }; 72 | struct ClusterOffsetsAndCounts { 73 | // each u32 contains a 24-bit index into ClusterLightIndexLists in the high 24 bits 74 | // and an 8-bit count of the number of lights in the low 8 bits 75 | data: array, 1024u>, 76 | }; 77 | #else 78 | struct PointLights { 79 | data: array, 80 | }; 81 | struct ClusterLightIndexLists { 82 | data: array, 83 | }; 84 | struct ClusterOffsetsAndCounts { 85 | data: array>, 86 | }; 87 | #endif 88 | -------------------------------------------------------------------------------- /examples/bevy_pbr_wgsl/pbr.wgsl: -------------------------------------------------------------------------------- 1 | #import bevy_pbr::mesh_vertex_output as OutputTypes 2 | #import bevy_pbr::pbr_functions as PbrCore 3 | #import bevy_pbr::pbr_bindings as MaterialBindings 4 | #import bevy_pbr::pbr_types as PbrTypes 5 | #import bevy_pbr::mesh_view_bindings as ViewBindings 6 | 7 | @fragment 8 | fn fragment( 9 | mesh: OutputTypes::MeshVertexOutput, 10 | @builtin(front_facing) is_front: bool, 11 | @builtin(position) frag_coord: vec4, 12 | ) -> @location(0) vec4 { 13 | var output_color: vec4 = MaterialBindings::material.base_color; 14 | 15 | #ifdef VERTEX_COLORS 16 | output_color = output_color * mesh.color; 17 | #endif 18 | #ifdef VERTEX_UVS 19 | if ((MaterialBindings::material.flags & PbrTypes::STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u) { 20 | output_color = output_color * textureSample(MaterialBindings::base_color_texture, MaterialBindings::base_color_sampler, mesh.uv); 21 | } 22 | #endif 23 | 24 | // NOTE: Unlit bit not set means == 0 is true, so the true case is if lit 25 | if ((MaterialBindings::material.flags & PbrTypes::STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u) { 26 | // Prepare a 'processed' StandardMaterial by sampling all textures to resolve 27 | // the material members 28 | var pbr_input: PbrCore::PbrInput; 29 | 30 | pbr_input.material.base_color = output_color; 31 | pbr_input.material.reflectance = MaterialBindings::material.reflectance; 32 | pbr_input.material.flags = MaterialBindings::material.flags; 33 | pbr_input.material.alpha_cutoff = MaterialBindings::material.alpha_cutoff; 34 | 35 | // TODO use .a for exposure compensation in HDR 36 | var emissive: vec4 = MaterialBindings::material.emissive; 37 | #ifdef VERTEX_UVS 38 | if ((MaterialBindings::material.flags & PbrTypes::STANDARD_MATERIAL_FLAGS_EMISSIVE_TEXTURE_BIT) != 0u) { 39 | emissive = vec4(emissive.rgb * textureSample(MaterialBindings::emissive_texture, MaterialBindings::emissive_sampler, mesh.uv).rgb, 1.0); 40 | } 41 | #endif 42 | pbr_input.material.emissive = emissive; 43 | 44 | var metallic: f32 = MaterialBindings::material.metallic; 45 | var perceptual_roughness: f32 = MaterialBindings::material.perceptual_roughness; 46 | #ifdef VERTEX_UVS 47 | if ((MaterialBindings::material.flags & PbrTypes::STANDARD_MATERIAL_FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT) != 0u) { 48 | let metallic_roughness = textureSample(MaterialBindings::metallic_roughness_texture, MaterialBindings::metallic_roughness_sampler, mesh.uv); 49 | // Sampling from GLTF standard channels for now 50 | metallic = metallic * metallic_roughness.b; 51 | perceptual_roughness = perceptual_roughness * metallic_roughness.g; 52 | } 53 | #endif 54 | pbr_input.material.metallic = metallic; 55 | pbr_input.material.perceptual_roughness = perceptual_roughness; 56 | 57 | var occlusion: f32 = 1.0; 58 | #ifdef VERTEX_UVS 59 | if ((MaterialBindings::material.flags & PbrTypes::STANDARD_MATERIAL_FLAGS_OCCLUSION_TEXTURE_BIT) != 0u) { 60 | occlusion = textureSample(MaterialBindings::occlusion_texture, MaterialBindings::occlusion_sampler, mesh.uv).r; 61 | } 62 | #endif 63 | pbr_input.occlusion = occlusion; 64 | 65 | pbr_input.frag_coord = frag_coord; 66 | pbr_input.world_position = mesh.world_position; 67 | pbr_input.world_normal = mesh.world_normal; 68 | 69 | pbr_input.is_orthographic = ViewBindings::view.projection[3].w == 1.0; 70 | 71 | pbr_input.N = PbrCore::prepare_normal( 72 | MaterialBindings::material.flags, 73 | mesh.world_normal, 74 | #ifdef VERTEX_TANGENTS 75 | #ifdef STANDARDMATERIAL_NORMAL_MAP 76 | mesh.world_tangent, 77 | #endif 78 | #endif 79 | #ifdef VERTEX_UVS 80 | mesh.uv, 81 | #endif 82 | is_front, 83 | ); 84 | pbr_input.V = PbrCore::calculate_view(mesh.world_position, pbr_input.is_orthographic); 85 | 86 | output_color = PbrCore::tone_mapping(PbrCore::pbr(pbr_input)); 87 | } 88 | 89 | return output_color; 90 | } 91 | -------------------------------------------------------------------------------- /examples/bevy_pbr_wgsl/pbr_bindings.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path bevy_pbr::pbr_bindings 2 | 3 | #import bevy_pbr::pbr_types as Types 4 | 5 | @group(1) @binding(0) 6 | var material: Types::StandardMaterial; 7 | @group(1) @binding(1) 8 | var base_color_texture: texture_2d; 9 | @group(1) @binding(2) 10 | var base_color_sampler: sampler; 11 | @group(1) @binding(3) 12 | var emissive_texture: texture_2d; 13 | @group(1) @binding(4) 14 | var emissive_sampler: sampler; 15 | @group(1) @binding(5) 16 | var metallic_roughness_texture: texture_2d; 17 | @group(1) @binding(6) 18 | var metallic_roughness_sampler: sampler; 19 | @group(1) @binding(7) 20 | var occlusion_texture: texture_2d; 21 | @group(1) @binding(8) 22 | var occlusion_sampler: sampler; 23 | @group(1) @binding(9) 24 | var normal_map_texture: texture_2d; 25 | @group(1) @binding(10) 26 | var normal_map_sampler: sampler; 27 | -------------------------------------------------------------------------------- /examples/bevy_pbr_wgsl/pbr_functions.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path bevy_pbr::pbr_functions 2 | 3 | #import bevy_pbr::pbr_types as PbrTypes 4 | #import bevy_pbr::mesh_types as MeshTypes 5 | #import bevy_pbr::mesh_bindings as MeshBindings 6 | #import bevy_pbr::mesh_view_types as ViewTypes 7 | #import bevy_pbr::mesh_view_bindings as ViewBindings 8 | #import bevy_pbr::lighting as Lighting 9 | #import bevy_pbr::clustered_forward as Clustering 10 | #import bevy_pbr::shadows as Shadows 11 | 12 | // NOTE: This ensures that the world_normal is normalized and if 13 | // vertex tangents and normal maps then normal mapping may be applied. 14 | fn prepare_normal( 15 | standard_material_flags: u32, 16 | world_normal: vec3, 17 | #ifdef VERTEX_TANGENTS 18 | #ifdef STANDARDMATERIAL_NORMAL_MAP 19 | world_tangent: vec4, 20 | #endif 21 | #endif 22 | #ifdef VERTEX_UVS 23 | uv: vec2, 24 | #endif 25 | is_front: bool, 26 | ) -> vec3 { 27 | var N: vec3 = normalize(world_normal); 28 | 29 | #ifdef VERTEX_TANGENTS 30 | #ifdef STANDARDMATERIAL_NORMAL_MAP 31 | // NOTE: The mikktspace method of normal mapping explicitly requires that these NOT be 32 | // normalized nor any Gram-Schmidt applied to ensure the vertex normal is orthogonal to the 33 | // vertex tangent! Do not change this code unless you really know what you are doing. 34 | // http://www.mikktspace.com/ 35 | var T: vec3 = world_tangent.xyz; 36 | var B: vec3 = world_tangent.w * cross(N, T); 37 | #endif 38 | #endif 39 | 40 | if ((standard_material_flags & PbrTypes::STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u) { 41 | if (!is_front) { 42 | N = -N; 43 | #ifdef VERTEX_TANGENTS 44 | #ifdef STANDARDMATERIAL_NORMAL_MAP 45 | T = -T; 46 | B = -B; 47 | #endif 48 | #endif 49 | } 50 | } 51 | 52 | #ifdef VERTEX_TANGENTS 53 | #ifdef VERTEX_UVS 54 | #ifdef STANDARDMATERIAL_NORMAL_MAP 55 | // Nt is the tangent-space normal. 56 | var Nt = textureSample(normal_map_texture, normal_map_sampler, uv).rgb; 57 | if ((standard_material_flags & PbrTypes::STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP) != 0u) { 58 | // Only use the xy components and derive z for 2-component normal maps. 59 | Nt = vec3(Nt.rg * 2.0 - 1.0, 0.0); 60 | Nt.z = sqrt(1.0 - Nt.x * Nt.x - Nt.y * Nt.y); 61 | } else { 62 | Nt = Nt * 2.0 - 1.0; 63 | } 64 | // Normal maps authored for DirectX require flipping the y component 65 | if ((standard_material_flags & PbrTypes::STANDARD_MATERIAL_FLAGS_FLIP_NORMAL_MAP_Y) != 0u) { 66 | Nt.y = -Nt.y; 67 | } 68 | // NOTE: The mikktspace method of normal mapping applies maps the tangent-space normal from 69 | // the normal map texture in this way to be an EXACT inverse of how the normal map baker 70 | // calculates the normal maps so there is no error introduced. Do not change this code 71 | // unless you really know what you are doing. 72 | // http://www.mikktspace.com/ 73 | N = normalize(Nt.x * T + Nt.y * B + Nt.z * N); 74 | #endif 75 | #endif 76 | #endif 77 | 78 | return N; 79 | } 80 | 81 | // NOTE: Correctly calculates the view vector depending on whether 82 | // the projection is orthographic or perspective. 83 | fn calculate_view( 84 | world_position: vec4, 85 | is_orthographic: bool, 86 | ) -> vec3 { 87 | var V: vec3; 88 | if (is_orthographic) { 89 | // Orthographic view vector 90 | V = normalize(vec3(ViewBindings::view.view_proj[0].z, ViewBindings::view.view_proj[1].z, ViewBindings::view.view_proj[2].z)); 91 | } else { 92 | // Only valid for a perpective projection 93 | V = normalize(ViewBindings::view.world_position.xyz - world_position.xyz); 94 | } 95 | return V; 96 | } 97 | 98 | struct PbrInput { 99 | material: PbrTypes::StandardMaterial, 100 | occlusion: f32, 101 | frag_coord: vec4, 102 | world_position: vec4, 103 | // Normalized world normal used for shadow mapping as normal-mapping is not used for shadow 104 | // mapping 105 | world_normal: vec3, 106 | // Normalized normal-mapped world normal used for lighting 107 | N: vec3, 108 | // Normalized view vector in world space, pointing from the fragment world position toward the 109 | // view world position 110 | V: vec3, 111 | is_orthographic: bool, 112 | }; 113 | 114 | // Creates a PbrInput with default values 115 | fn pbr_input_new() -> PbrInput { 116 | var pbr_input: PbrInput; 117 | 118 | pbr_input.material = PbrTypes::standard_material_new(); 119 | pbr_input.occlusion = 1.0; 120 | 121 | pbr_input.frag_coord = vec4(0.0, 0.0, 0.0, 1.0); 122 | pbr_input.world_position = vec4(0.0, 0.0, 0.0, 1.0); 123 | pbr_input.world_normal = vec3(0.0, 0.0, 1.0); 124 | 125 | pbr_input.is_orthographic = false; 126 | 127 | pbr_input.N = vec3(0.0, 0.0, 1.0); 128 | pbr_input.V = vec3(1.0, 0.0, 0.0); 129 | 130 | return pbr_input; 131 | } 132 | 133 | fn pbr( 134 | in: PbrInput, 135 | ) -> vec4 { 136 | var output_color: vec4 = in.material.base_color; 137 | 138 | // TODO use .a for exposure compensation in HDR 139 | let emissive = in.material.emissive; 140 | 141 | // calculate non-linear roughness from linear perceptualRoughness 142 | let metallic = in.material.metallic; 143 | let perceptual_roughness = in.material.perceptual_roughness; 144 | let roughness = Lighting::perceptualRoughnessToRoughness(perceptual_roughness); 145 | 146 | let occlusion = in.occlusion; 147 | 148 | if ((in.material.flags & PbrTypes::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE) != 0u) { 149 | // NOTE: If rendering as opaque, alpha should be ignored so set to 1.0 150 | output_color.a = 1.0; 151 | } else if ((in.material.flags & PbrTypes::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK) != 0u) { 152 | if (output_color.a >= in.material.alpha_cutoff) { 153 | // NOTE: If rendering as masked alpha and >= the cutoff, render as fully opaque 154 | output_color.a = 1.0; 155 | } else { 156 | // NOTE: output_color.a < in.material.alpha_cutoff should not is not rendered 157 | // NOTE: This and any other discards mean that early-z testing cannot be done! 158 | discard; 159 | } 160 | } 161 | 162 | // Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886" 163 | let NdotV = max(dot(in.N, in.V), 0.0001); 164 | 165 | // Remapping [0,1] reflectance to F0 166 | // See https://google.github.io/filament/Filament.html#materialsystem/parameterization/remapping 167 | let reflectance = in.material.reflectance; 168 | let F0 = 0.16 * reflectance * reflectance * (1.0 - metallic) + output_color.rgb * metallic; 169 | 170 | // Diffuse strength inversely related to metallicity 171 | let diffuse_color = output_color.rgb * (1.0 - metallic); 172 | 173 | let R = reflect(-in.V, in.N); 174 | 175 | // accumulate color 176 | var light_accum: vec3 = vec3(0.0); 177 | 178 | let view_z = dot(vec4( 179 | ViewBindings::view.inverse_view[0].z, 180 | ViewBindings::view.inverse_view[1].z, 181 | ViewBindings::view.inverse_view[2].z, 182 | ViewBindings::view.inverse_view[3].z 183 | ), in.world_position); 184 | let cluster_index = Clustering::fragment_cluster_index(in.frag_coord.xy, view_z, in.is_orthographic); 185 | let offset_and_counts = Clustering::unpack_offset_and_counts(cluster_index); 186 | 187 | // point lights 188 | for (var i: u32 = offset_and_counts[0]; i < offset_and_counts[0] + offset_and_counts[1]; i = i + 1u) { 189 | let light_id = Clustering::get_light_id(i); 190 | let light = ViewBindings::point_lights.data[light_id]; 191 | var shadow: f32 = 1.0; 192 | if ((MeshBindings::mesh.flags & MeshTypes::MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u 193 | && (light.flags & ViewTypes::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { 194 | shadow = Shadows::fetch_point_shadow(light_id, in.world_position, in.world_normal); 195 | } 196 | let light_contrib = Lighting::point_light(in.world_position.xyz, light, roughness, NdotV, in.N, in.V, R, F0, diffuse_color); 197 | light_accum = light_accum + light_contrib * shadow; 198 | } 199 | 200 | // spot lights 201 | for (var i: u32 = offset_and_counts[0] + offset_and_counts[1]; i < offset_and_counts[0] + offset_and_counts[1] + offset_and_counts[2]; i = i + 1u) { 202 | let light_id = Clustering::get_light_id(i); 203 | let light = ViewBindings::point_lights.data[light_id]; 204 | var shadow: f32 = 1.0; 205 | if ((MeshBindings::mesh.flags & MeshTypes::MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u 206 | && (light.flags & ViewTypes::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { 207 | shadow = Shadows::fetch_spot_shadow(light_id, in.world_position, in.world_normal); 208 | } 209 | let light_contrib = Lighting::spot_light(in.world_position.xyz, light, roughness, NdotV, in.N, in.V, R, F0, diffuse_color); 210 | light_accum = light_accum + light_contrib * shadow; 211 | } 212 | 213 | let n_directional_lights = ViewBindings::lights.n_directional_lights; 214 | for (var i: u32 = 0u; i < n_directional_lights; i = i + 1u) { 215 | let light = ViewBindings::lights.directional_lights[i]; 216 | var shadow: f32 = 1.0; 217 | if ((MeshBindings::mesh.flags & MeshTypes::MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u 218 | && (light.flags & ViewTypes::DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { 219 | shadow = Shadows::fetch_directional_shadow(i, in.world_position, in.world_normal); 220 | } 221 | let light_contrib = Lighting::directional_light(light, roughness, NdotV, in.N, in.V, R, F0, diffuse_color); 222 | light_accum = light_accum + light_contrib * shadow; 223 | } 224 | 225 | let diffuse_ambient = Lighting::EnvBRDFApprox(diffuse_color, 1.0, NdotV); 226 | let specular_ambient = Lighting::EnvBRDFApprox(F0, perceptual_roughness, NdotV); 227 | 228 | output_color = vec4( 229 | light_accum + 230 | (diffuse_ambient + specular_ambient) * ViewBindings::lights.ambient_color.rgb * occlusion + 231 | emissive.rgb * output_color.a, 232 | output_color.a); 233 | 234 | output_color = Clustering::cluster_debug_visualization( 235 | output_color, 236 | view_z, 237 | in.is_orthographic, 238 | offset_and_counts, 239 | cluster_index, 240 | ); 241 | 242 | return output_color; 243 | } 244 | 245 | fn tone_mapping(in: vec4) -> vec4 { 246 | // tone_mapping 247 | return vec4(Lighting::reinhard_luminance(in.rgb), in.a); 248 | 249 | // Gamma correction. 250 | // Not needed with sRGB buffer 251 | // output_color.rgb = pow(output_color.rgb, vec3(1.0 / 2.2)); 252 | } 253 | -------------------------------------------------------------------------------- /examples/bevy_pbr_wgsl/pbr_lighting.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path bevy_pbr::lighting 2 | 3 | #import bevy_pbr::utils as Utils 4 | #import bevy_pbr::mesh_view_types as ViewTypes 5 | 6 | // From the Filament design doc 7 | // https://google.github.io/filament/Filament.html#table_symbols 8 | // Symbol Definition 9 | // v View unit vector 10 | // l Incident light unit vector 11 | // n Surface normal unit vector 12 | // h Half unit vector between l and v 13 | // f BRDF 14 | // f_d Diffuse component of a BRDF 15 | // f_r Specular component of a BRDF 16 | // α Roughness, remapped from using input perceptualRoughness 17 | // σ Diffuse reflectance 18 | // Ω Spherical domain 19 | // f0 Reflectance at normal incidence 20 | // f90 Reflectance at grazing angle 21 | // χ+(a) Heaviside function (1 if a>0 and 0 otherwise) 22 | // nior Index of refraction (IOR) of an interface 23 | // ⟨n⋅l⟩ Dot product clamped to [0..1] 24 | // ⟨a⟩ Saturated value (clamped to [0..1]) 25 | 26 | // The Bidirectional Reflectance Distribution Function (BRDF) describes the surface response of a standard material 27 | // and consists of two components, the diffuse component (f_d) and the specular component (f_r): 28 | // f(v,l) = f_d(v,l) + f_r(v,l) 29 | // 30 | // The form of the microfacet model is the same for diffuse and specular 31 | // f_r(v,l) = f_d(v,l) = 1 / { |n⋅v||n⋅l| } ∫_Ω D(m,α) G(v,l,m) f_m(v,l,m) (v⋅m) (l⋅m) dm 32 | // 33 | // In which: 34 | // D, also called the Normal Distribution Function (NDF) models the distribution of the microfacets 35 | // G models the visibility (or occlusion or shadow-masking) of the microfacets 36 | // f_m is the microfacet BRDF and differs between specular and diffuse components 37 | // 38 | // The above integration needs to be approximated. 39 | 40 | // distanceAttenuation is simply the square falloff of light intensity 41 | // combined with a smooth attenuation at the edge of the light radius 42 | // 43 | // light radius is a non-physical construct for efficiency purposes, 44 | // because otherwise every light affects every fragment in the scene 45 | fn getDistanceAttenuation(distanceSquare: f32, inverseRangeSquared: f32) -> f32 { 46 | let factor = distanceSquare * inverseRangeSquared; 47 | let smoothFactor = Utils::saturate(1.0 - factor * factor); 48 | let attenuation = smoothFactor * smoothFactor; 49 | return attenuation * 1.0 / max(distanceSquare, 0.0001); 50 | } 51 | 52 | // Normal distribution function (specular D) 53 | // Based on https://google.github.io/filament/Filament.html#citation-walter07 54 | 55 | // D_GGX(h,α) = α^2 / { π ((n⋅h)^2 (α2−1) + 1)^2 } 56 | 57 | // Simple implementation, has precision problems when using fp16 instead of fp32 58 | // see https://google.github.io/filament/Filament.html#listing_speculardfp16 59 | fn D_GGX(roughness: f32, NoH: f32, h: vec3) -> f32 { 60 | let oneMinusNoHSquared = 1.0 - NoH * NoH; 61 | let a = NoH * roughness; 62 | let k = roughness / (oneMinusNoHSquared + a * a); 63 | let d = k * k * (1.0 / Utils::PI); 64 | return d; 65 | } 66 | 67 | // Visibility function (Specular G) 68 | // V(v,l,a) = G(v,l,α) / { 4 (n⋅v) (n⋅l) } 69 | // such that f_r becomes 70 | // f_r(v,l) = D(h,α) V(v,l,α) F(v,h,f0) 71 | // where 72 | // V(v,l,α) = 0.5 / { n⋅l sqrt((n⋅v)^2 (1−α2) + α2) + n⋅v sqrt((n⋅l)^2 (1−α2) + α2) } 73 | // Note the two sqrt's, that may be slow on mobile, see https://google.github.io/filament/Filament.html#listing_approximatedspecularv 74 | fn V_SmithGGXCorrelated(roughness: f32, NoV: f32, NoL: f32) -> f32 { 75 | let a2 = roughness * roughness; 76 | let lambdaV = NoL * sqrt((NoV - a2 * NoV) * NoV + a2); 77 | let lambdaL = NoV * sqrt((NoL - a2 * NoL) * NoL + a2); 78 | let v = 0.5 / (lambdaV + lambdaL); 79 | return v; 80 | } 81 | 82 | // Fresnel function 83 | // see https://google.github.io/filament/Filament.html#citation-schlick94 84 | // F_Schlick(v,h,f_0,f_90) = f_0 + (f_90 − f_0) (1 − v⋅h)^5 85 | fn F_Schlick_vec(f0: vec3, f90: f32, VoH: f32) -> vec3 { 86 | // not using mix to keep the vec3 and float versions identical 87 | return f0 + (f90 - f0) * pow(1.0 - VoH, 5.0); 88 | } 89 | 90 | fn F_Schlick(f0: f32, f90: f32, VoH: f32) -> f32 { 91 | // not using mix to keep the vec3 and float versions identical 92 | return f0 + (f90 - f0) * pow(1.0 - VoH, 5.0); 93 | } 94 | 95 | fn fresnel(f0: vec3, LoH: f32) -> vec3 { 96 | // f_90 suitable for ambient occlusion 97 | // see https://google.github.io/filament/Filament.html#lighting/occlusion 98 | let f90 = Utils::saturate(dot(f0, vec3(50.0 * 0.33))); 99 | return F_Schlick_vec(f0, f90, LoH); 100 | } 101 | 102 | // Specular BRDF 103 | // https://google.github.io/filament/Filament.html#materialsystem/specularbrdf 104 | 105 | // Cook-Torrance approximation of the microfacet model integration using Fresnel law F to model f_m 106 | // f_r(v,l) = { D(h,α) G(v,l,α) F(v,h,f0) } / { 4 (n⋅v) (n⋅l) } 107 | fn specular(f0: vec3, roughness: f32, h: vec3, NoV: f32, NoL: f32, 108 | NoH: f32, LoH: f32, specularIntensity: f32) -> vec3 { 109 | let D = D_GGX(roughness, NoH, h); 110 | let V = V_SmithGGXCorrelated(roughness, NoV, NoL); 111 | let F = fresnel(f0, LoH); 112 | 113 | return (specularIntensity * D * V) * F; 114 | } 115 | 116 | // Diffuse BRDF 117 | // https://google.github.io/filament/Filament.html#materialsystem/diffusebrdf 118 | // fd(v,l) = σ/π * 1 / { |n⋅v||n⋅l| } ∫Ω D(m,α) G(v,l,m) (v⋅m) (l⋅m) dm 119 | // 120 | // simplest approximation 121 | // float Fd_Lambert() { 122 | // return 1.0 / PI; 123 | // } 124 | // 125 | // vec3 Fd = diffuseColor * Fd_Lambert(); 126 | // 127 | // Disney approximation 128 | // See https://google.github.io/filament/Filament.html#citation-burley12 129 | // minimal quality difference 130 | fn Fd_Burley(roughness: f32, NoV: f32, NoL: f32, LoH: f32) -> f32 { 131 | let f90 = 0.5 + 2.0 * roughness * LoH * LoH; 132 | let lightScatter = F_Schlick(1.0, f90, NoL); 133 | let viewScatter = F_Schlick(1.0, f90, NoV); 134 | return lightScatter * viewScatter * (1.0 / Utils::PI); 135 | } 136 | 137 | // From https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile 138 | fn EnvBRDFApprox(f0: vec3, perceptual_roughness: f32, NoV: f32) -> vec3 { 139 | let c0 = vec4(-1.0, -0.0275, -0.572, 0.022); 140 | let c1 = vec4(1.0, 0.0425, 1.04, -0.04); 141 | let r = perceptual_roughness * c0 + c1; 142 | let a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y; 143 | let AB = vec2(-1.04, 1.04) * a004 + r.zw; 144 | return f0 * AB.x + AB.y; 145 | } 146 | 147 | fn perceptualRoughnessToRoughness(perceptualRoughness: f32) -> f32 { 148 | // clamp perceptual roughness to prevent precision problems 149 | // According to Filament design 0.089 is recommended for mobile 150 | // Filament uses 0.045 for non-mobile 151 | let clampedPerceptualRoughness = clamp(perceptualRoughness, 0.089, 1.0); 152 | return clampedPerceptualRoughness * clampedPerceptualRoughness; 153 | } 154 | 155 | // from https://64.github.io/tonemapping/ 156 | // reinhard on RGB oversaturates colors 157 | fn reinhard(color: vec3) -> vec3 { 158 | return color / (1.0 + color); 159 | } 160 | 161 | fn reinhard_extended(color: vec3, max_white: f32) -> vec3 { 162 | let numerator = color * (1.0 + (color / vec3(max_white * max_white))); 163 | return numerator / (1.0 + color); 164 | } 165 | 166 | // luminance coefficients from Rec. 709. 167 | // https://en.wikipedia.org/wiki/Rec._709 168 | fn luminance(v: vec3) -> f32 { 169 | return dot(v, vec3(0.2126, 0.7152, 0.0722)); 170 | } 171 | 172 | fn change_luminance(c_in: vec3, l_out: f32) -> vec3 { 173 | let l_in = luminance(c_in); 174 | return c_in * (l_out / l_in); 175 | } 176 | 177 | fn reinhard_luminance(color: vec3) -> vec3 { 178 | let l_old = luminance(color); 179 | let l_new = l_old / (1.0 + l_old); 180 | return change_luminance(color, l_new); 181 | } 182 | 183 | fn reinhard_extended_luminance(color: vec3, max_white_l: f32) -> vec3 { 184 | let l_old = luminance(color); 185 | let numerator = l_old * (1.0 + (l_old / (max_white_l * max_white_l))); 186 | let l_new = numerator / (1.0 + l_old); 187 | return change_luminance(color, l_new); 188 | } 189 | 190 | fn point_light( 191 | world_position: vec3, light: ViewTypes::PointLight, roughness: f32, NdotV: f32, N: vec3, V: vec3, 192 | R: vec3, F0: vec3, diffuseColor: vec3 193 | ) -> vec3 { 194 | let light_to_frag = light.position_radius.xyz - world_position.xyz; 195 | let distance_square = dot(light_to_frag, light_to_frag); 196 | let rangeAttenuation = 197 | getDistanceAttenuation(distance_square, light.color_inverse_square_range.w); 198 | 199 | // Specular. 200 | // Representative Point Area Lights. 201 | // see http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf p14-16 202 | let a = roughness; 203 | let centerToRay = dot(light_to_frag, R) * R - light_to_frag; 204 | let closestPoint = light_to_frag + centerToRay * Utils::saturate(light.position_radius.w * inverseSqrt(dot(centerToRay, centerToRay))); 205 | let LspecLengthInverse = inverseSqrt(dot(closestPoint, closestPoint)); 206 | let normalizationFactor = a / Utils::saturate(a + (light.position_radius.w * 0.5 * LspecLengthInverse)); 207 | let specularIntensity = normalizationFactor * normalizationFactor; 208 | 209 | var L: vec3 = closestPoint * LspecLengthInverse; // normalize() equivalent? 210 | var H: vec3 = normalize(L + V); 211 | var NoL: f32 = Utils::saturate(dot(N, L)); 212 | var NoH: f32 = Utils::saturate(dot(N, H)); 213 | var LoH: f32 = Utils::saturate(dot(L, H)); 214 | 215 | let specular_light = specular(F0, roughness, H, NdotV, NoL, NoH, LoH, specularIntensity); 216 | 217 | // Diffuse. 218 | // Comes after specular since its NoL is used in the lighting equation. 219 | L = normalize(light_to_frag); 220 | H = normalize(L + V); 221 | NoL = Utils::saturate(dot(N, L)); 222 | NoH = Utils::saturate(dot(N, H)); 223 | LoH = Utils::saturate(dot(L, H)); 224 | 225 | let diffuse = diffuseColor * Fd_Burley(roughness, NdotV, NoL, LoH); 226 | 227 | // See https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminanceEquation 228 | // Lout = f(v,l) Φ / { 4 π d^2 }⟨n⋅l⟩ 229 | // where 230 | // f(v,l) = (f_d(v,l) + f_r(v,l)) * light_color 231 | // Φ is luminous power in lumens 232 | // our rangeAttentuation = 1 / d^2 multiplied with an attenuation factor for smoothing at the edge of the non-physical maximum light radius 233 | 234 | // For a point light, luminous intensity, I, in lumens per steradian is given by: 235 | // I = Φ / 4 π 236 | // The derivation of this can be seen here: https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminousPower 237 | 238 | // NOTE: light.color.rgb is premultiplied with light.intensity / 4 π (which would be the luminous intensity) on the CPU 239 | 240 | // TODO compensate for energy loss https://google.github.io/filament/Filament.html#materialsystem/improvingthebrdfs/energylossinspecularreflectance 241 | 242 | return ((diffuse + specular_light) * light.color_inverse_square_range.rgb) * (rangeAttenuation * NoL); 243 | } 244 | 245 | fn spot_light( 246 | world_position: vec3, light: ViewTypes::PointLight, roughness: f32, NdotV: f32, N: vec3, V: vec3, 247 | R: vec3, F0: vec3, diffuseColor: vec3 248 | ) -> vec3 { 249 | // reuse the point light calculations 250 | let point_light = point_light(world_position, light, roughness, NdotV, N, V, R, F0, diffuseColor); 251 | 252 | // reconstruct spot dir from x/z and y-direction flag 253 | var spot_dir = vec3(light.light_custom_data.x, 0.0, light.light_custom_data.y); 254 | spot_dir.y = sqrt(1.0 - spot_dir.x * spot_dir.x - spot_dir.z * spot_dir.z); 255 | if ((light.flags & ViewTypes::POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE) != 0u) { 256 | spot_dir.y = -spot_dir.y; 257 | } 258 | let light_to_frag = light.position_radius.xyz - world_position.xyz; 259 | 260 | // calculate attenuation based on filament formula https://google.github.io/filament/Filament.html#listing_glslpunctuallight 261 | // spot_scale and spot_offset have been precomputed 262 | // note we normalize here to get "l" from the filament listing. spot_dir is already normalized 263 | let cd = dot(-spot_dir, normalize(light_to_frag)); 264 | let attenuation = Utils::saturate(cd * light.light_custom_data.z + light.light_custom_data.w); 265 | let spot_attenuation = attenuation * attenuation; 266 | 267 | return point_light * spot_attenuation; 268 | } 269 | 270 | fn directional_light(light: ViewTypes::DirectionalLight, roughness: f32, NdotV: f32, normal: vec3, view: vec3, R: vec3, F0: vec3, diffuseColor: vec3) -> vec3 { 271 | let incident_light = light.direction_to_light.xyz; 272 | 273 | let half_vector = normalize(incident_light + view); 274 | let NoL = Utils::saturate(dot(normal, incident_light)); 275 | let NoH = Utils::saturate(dot(normal, half_vector)); 276 | let LoH = Utils::saturate(dot(incident_light, half_vector)); 277 | 278 | let diffuse = diffuseColor * Fd_Burley(roughness, NdotV, NoL, LoH); 279 | let specularIntensity = 1.0; 280 | let specular_light = specular(F0, roughness, half_vector, NdotV, NoL, NoH, LoH, specularIntensity); 281 | 282 | return (specular_light + diffuse) * light.color.rgb * NoL; 283 | } 284 | -------------------------------------------------------------------------------- /examples/bevy_pbr_wgsl/pbr_types.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path bevy_pbr::pbr_types 2 | 3 | struct StandardMaterial { 4 | base_color: vec4, 5 | emissive: vec4, 6 | perceptual_roughness: f32, 7 | metallic: f32, 8 | reflectance: f32, 9 | // 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options. 10 | flags: u32, 11 | alpha_cutoff: f32, 12 | }; 13 | 14 | const STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT: u32 = 1u; 15 | const STANDARD_MATERIAL_FLAGS_EMISSIVE_TEXTURE_BIT: u32 = 2u; 16 | const STANDARD_MATERIAL_FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT: u32 = 4u; 17 | const STANDARD_MATERIAL_FLAGS_OCCLUSION_TEXTURE_BIT: u32 = 8u; 18 | const STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT: u32 = 16u; 19 | const STANDARD_MATERIAL_FLAGS_UNLIT_BIT: u32 = 32u; 20 | const STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE: u32 = 64u; 21 | const STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK: u32 = 128u; 22 | const STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND: u32 = 256u; 23 | const STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP: u32 = 512u; 24 | const STANDARD_MATERIAL_FLAGS_FLIP_NORMAL_MAP_Y: u32 = 1024u; 25 | 26 | // Creates a StandardMaterial with default values 27 | fn standard_material_new() -> StandardMaterial { 28 | var material: StandardMaterial; 29 | 30 | // NOTE: Keep in-sync with src/pbr_material.rs! 31 | material.base_color = vec4(1.0, 1.0, 1.0, 1.0); 32 | material.emissive = vec4(0.0, 0.0, 0.0, 1.0); 33 | material.perceptual_roughness = 0.089; 34 | material.metallic = 0.01; 35 | material.reflectance = 0.5; 36 | material.flags = STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE; 37 | material.alpha_cutoff = 0.5; 38 | 39 | return material; 40 | } 41 | -------------------------------------------------------------------------------- /examples/bevy_pbr_wgsl/shadows.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path bevy_pbr::shadows 2 | 3 | #import bevy_pbr::mesh_view_types as Types 4 | #import bevy_pbr::mesh_view_bindings as Bindings 5 | 6 | fn fetch_point_shadow(light_id: u32, frag_position: vec4, surface_normal: vec3) -> f32 { 7 | let light = Bindings::point_lights.data[light_id]; 8 | 9 | // because the shadow maps align with the axes and the frustum planes are at 45 degrees 10 | // we can get the worldspace depth by taking the largest absolute axis 11 | let surface_to_light = light.position_radius.xyz - frag_position.xyz; 12 | let surface_to_light_abs = abs(surface_to_light); 13 | let distance_to_light = max(surface_to_light_abs.x, max(surface_to_light_abs.y, surface_to_light_abs.z)); 14 | 15 | // The normal bias here is already scaled by the texel size at 1 world unit from the light. 16 | // The texel size increases proportionally with distance from the light so multiplying by 17 | // distance to light scales the normal bias to the texel size at the fragment distance. 18 | let normal_offset = light.shadow_normal_bias * distance_to_light * surface_normal.xyz; 19 | let depth_offset = light.shadow_depth_bias * normalize(surface_to_light.xyz); 20 | let offset_position = frag_position.xyz + normal_offset + depth_offset; 21 | 22 | // similar largest-absolute-axis trick as above, but now with the offset fragment position 23 | let frag_ls = light.position_radius.xyz - offset_position.xyz; 24 | let abs_position_ls = abs(frag_ls); 25 | let major_axis_magnitude = max(abs_position_ls.x, max(abs_position_ls.y, abs_position_ls.z)); 26 | 27 | // NOTE: These simplifications come from multiplying: 28 | // projection * vec4(0, 0, -major_axis_magnitude, 1.0) 29 | // and keeping only the terms that have any impact on the depth. 30 | // Projection-agnostic approach: 31 | let zw = -major_axis_magnitude * light.light_custom_data.xy + light.light_custom_data.zw; 32 | let depth = zw.x / zw.y; 33 | 34 | // do the lookup, using HW PCF and comparison 35 | // NOTE: Due to the non-uniform control flow above, we must use the Level variant of 36 | // textureSampleCompare to avoid undefined behaviour due to some of the fragments in 37 | // a quad (2x2 fragments) being processed not being sampled, and this messing with 38 | // mip-mapping functionality. The shadow maps have no mipmaps so Level just samples 39 | // from LOD 0. 40 | #ifdef NO_ARRAY_TEXTURES_SUPPORT 41 | return textureSampleCompare(Bindings::point_shadow_textures, Bindings::point_shadow_textures_sampler, frag_ls, depth); 42 | #else 43 | return textureSampleCompareLevel(Bindings::point_shadow_textures, Bindings::point_shadow_textures_sampler, frag_ls, i32(light_id), depth); 44 | #endif 45 | } 46 | 47 | fn fetch_spot_shadow(light_id: u32, frag_position: vec4, surface_normal: vec3) -> f32 { 48 | let light = Bindings::point_lights.data[light_id]; 49 | 50 | let surface_to_light = light.position_radius.xyz - frag_position.xyz; 51 | 52 | // construct the light view matrix 53 | var spot_dir = vec3(light.light_custom_data.x, 0.0, light.light_custom_data.y); 54 | // reconstruct spot dir from x/z and y-direction flag 55 | spot_dir.y = sqrt(1.0 - spot_dir.x * spot_dir.x - spot_dir.z * spot_dir.z); 56 | if ((light.flags & Types::POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE) != 0u) { 57 | spot_dir.y = -spot_dir.y; 58 | } 59 | 60 | // view matrix z_axis is the reverse of transform.forward() 61 | let fwd = -spot_dir; 62 | let distance_to_light = dot(fwd, surface_to_light); 63 | let offset_position = 64 | -surface_to_light 65 | + (light.shadow_depth_bias * normalize(surface_to_light)) 66 | + (surface_normal.xyz * light.shadow_normal_bias) * distance_to_light; 67 | 68 | // the construction of the up and right vectors needs to precisely mirror the code 69 | // in render/light.rs:spot_light_view_matrix 70 | var sign = -1.0; 71 | if (fwd.z >= 0.0) { 72 | sign = 1.0; 73 | } 74 | let a = -1.0 / (fwd.z + sign); 75 | let b = fwd.x * fwd.y * a; 76 | let up_dir = vec3(1.0 + sign * fwd.x * fwd.x * a, sign * b, -sign * fwd.x); 77 | let right_dir = vec3(-b, -sign - fwd.y * fwd.y * a, fwd.y); 78 | let light_inv_rot = mat3x3(right_dir, up_dir, fwd); 79 | 80 | // because the matrix is a pure rotation matrix, the inverse is just the transpose, and to calculate 81 | // the product of the transpose with a vector we can just post-multiply instead of pre-multplying. 82 | // this allows us to keep the matrix construction code identical between CPU and GPU. 83 | let projected_position = offset_position * light_inv_rot; 84 | 85 | // divide xy by perspective matrix "f" and by -projected.z (projected.z is -projection matrix's w) 86 | // to get ndc coordinates 87 | let f_div_minus_z = 1.0 / (light.spot_light_tan_angle * -projected_position.z); 88 | let shadow_xy_ndc = projected_position.xy * f_div_minus_z; 89 | // convert to uv coordinates 90 | let shadow_uv = shadow_xy_ndc * vec2(0.5, -0.5) + vec2(0.5, 0.5); 91 | 92 | // 0.1 must match POINT_LIGHT_NEAR_Z 93 | let depth = 0.1 / -projected_position.z; 94 | 95 | #ifdef NO_ARRAY_TEXTURES_SUPPORT 96 | return textureSampleCompare(Bindings::directional_shadow_textures, Bindings::directional_shadow_textures_sampler, 97 | shadow_uv, depth); 98 | #else 99 | return textureSampleCompareLevel(Bindings::directional_shadow_textures, Bindings::directional_shadow_textures_sampler, 100 | shadow_uv, i32(light_id) + Bindings::lights.spot_light_shadowmap_offset, depth); 101 | #endif 102 | } 103 | 104 | fn fetch_directional_shadow(light_id: u32, frag_position: vec4, surface_normal: vec3) -> f32 { 105 | let light = Bindings::lights.directional_lights[light_id]; 106 | 107 | // The normal bias is scaled to the texel size. 108 | let normal_offset = light.shadow_normal_bias * surface_normal.xyz; 109 | let depth_offset = light.shadow_depth_bias * light.direction_to_light.xyz; 110 | let offset_position = vec4(frag_position.xyz + normal_offset + depth_offset, frag_position.w); 111 | 112 | let offset_position_clip = light.view_projection * offset_position; 113 | if (offset_position_clip.w <= 0.0) { 114 | return 1.0; 115 | } 116 | let offset_position_ndc = offset_position_clip.xyz / offset_position_clip.w; 117 | // No shadow outside the orthographic projection volume 118 | if (any(offset_position_ndc.xy < vec2(-1.0)) || offset_position_ndc.z < 0.0 119 | || any(offset_position_ndc > vec3(1.0))) { 120 | return 1.0; 121 | } 122 | 123 | // compute texture coordinates for shadow lookup, compensating for the Y-flip difference 124 | // between the NDC and texture coordinates 125 | let flip_correction = vec2(0.5, -0.5); 126 | let light_local = offset_position_ndc.xy * flip_correction + vec2(0.5, 0.5); 127 | 128 | let depth = offset_position_ndc.z; 129 | // do the lookup, using HW PCF and comparison 130 | // NOTE: Due to non-uniform control flow above, we must use the level variant of the texture 131 | // sampler to avoid use of implicit derivatives causing possible undefined behavior. 132 | #ifdef NO_ARRAY_TEXTURES_SUPPORT 133 | return textureSampleCompareLevel(Bindings::directional_shadow_textures, Bindings::directional_shadow_textures_sampler, light_local, depth); 134 | #else 135 | return textureSampleCompareLevel(Bindings::directional_shadow_textures, Bindings::directional_shadow_textures_sampler, light_local, i32(light_id), depth); 136 | #endif 137 | } 138 | -------------------------------------------------------------------------------- /examples/bevy_pbr_wgsl/skinning.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path bevy_pbr::skinning 2 | 3 | #ifdef SKINNED 4 | 5 | @group(2) @binding(1) 6 | var joint_matrices: SkinnedMesh; 7 | 8 | fn skin_model( 9 | indexes: vec4, 10 | weights: vec4, 11 | ) -> mat4x4 { 12 | return weights.x * joint_matrices.data[indexes.x] 13 | + weights.y * joint_matrices.data[indexes.y] 14 | + weights.z * joint_matrices.data[indexes.z] 15 | + weights.w * joint_matrices.data[indexes.w]; 16 | } 17 | 18 | fn inverse_transpose_3x3(in: mat3x3) -> mat3x3 { 19 | let x = cross(in[1], in[2]); 20 | let y = cross(in[2], in[0]); 21 | let z = cross(in[0], in[1]); 22 | let det = dot(in[2], z); 23 | return mat3x3( 24 | x / det, 25 | y / det, 26 | z / det 27 | ); 28 | } 29 | 30 | fn skin_normals( 31 | model: mat4x4, 32 | normal: vec3, 33 | ) -> vec3 { 34 | return inverse_transpose_3x3(mat3x3( 35 | model[0].xyz, 36 | model[1].xyz, 37 | model[2].xyz 38 | )) * normal; 39 | } 40 | 41 | #endif -------------------------------------------------------------------------------- /examples/bevy_pbr_wgsl/utils.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path bevy_pbr::utils 2 | 3 | const PI: f32 = 3.141592653589793; 4 | 5 | fn saturate(value: f32) -> f32 { 6 | return clamp(value, 0.0, 1.0); 7 | } 8 | 9 | fn hsv2rgb(hue: f32, saturation: f32, value: f32) -> vec3 { 10 | let rgb = clamp( 11 | abs( 12 | ((hue * 6.0 + vec3(0.0, 4.0, 2.0)) % 6.0) - 3.0 13 | ) - 1.0, 14 | vec3(0.0), 15 | vec3(1.0) 16 | ); 17 | 18 | return value * mix( vec3(1.0), rgb, vec3(saturation)); 19 | } 20 | 21 | fn random1D(s: f32) -> f32 { 22 | return fract(sin(s * 12.9898) * 43758.5453123); 23 | } 24 | -------------------------------------------------------------------------------- /examples/bevy_pbr_wgsl/wireframe.wgsl: -------------------------------------------------------------------------------- 1 | #import bevy_pbr::mesh_types 2 | #import bevy_pbr::mesh_view_bindings 3 | 4 | @group(1) @binding(0) 5 | var mesh: Mesh; 6 | 7 | #ifdef SKINNED 8 | @group(1) @binding(1) 9 | var joint_matrices: SkinnedMesh; 10 | #import bevy_pbr::skinning 11 | #endif 12 | 13 | // NOTE: Bindings must come before functions that use them! 14 | #import bevy_pbr::mesh_functions 15 | 16 | struct Vertex { 17 | @location(0) position: vec3, 18 | #ifdef SKINNED 19 | @location(4) joint_indexes: vec4, 20 | @location(5) joint_weights: vec4, 21 | #endif 22 | }; 23 | 24 | struct VertexOutput { 25 | @builtin(position) clip_position: vec4, 26 | }; 27 | 28 | @vertex 29 | fn vertex(vertex: Vertex) -> VertexOutput { 30 | #ifdef SKINNED 31 | let model = skin_model(vertex.joint_indexes, vertex.joint_weights); 32 | #else 33 | let model = mesh.model; 34 | #endif 35 | 36 | var out: VertexOutput; 37 | out.clip_position = mesh_position_local_to_clip(model, vec4(vertex.position, 1.0)); 38 | return out; 39 | } 40 | 41 | @fragment 42 | fn fragment() -> @location(0) vec4 { 43 | return vec4(1.0, 1.0, 1.0, 1.0); 44 | } 45 | -------------------------------------------------------------------------------- /examples/pbr_compose_test.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use naga_oil::compose::{ 4 | ComposableModuleDescriptor, Composer, ComposerError, NagaModuleDescriptor, 5 | }; 6 | #[allow(unused_variables, dead_code)] 7 | 8 | fn init_composer() -> Composer { 9 | let mut composer = Composer::default(); 10 | 11 | let mut load_composable = |source: &str, file_path: &str| { 12 | match composer.add_composable_module(ComposableModuleDescriptor { 13 | source, 14 | file_path, 15 | ..Default::default() 16 | }) { 17 | Ok(_module) => { 18 | // println!("{} -> {:#?}", module.name, module) 19 | } 20 | Err(e) => { 21 | println!("? -> {e:#?}") 22 | } 23 | } 24 | }; 25 | 26 | load_composable( 27 | include_str!("bevy_pbr_wgsl/utils.wgsl"), 28 | "examples/bevy_pbr_wgsl/utils.wgsl", 29 | ); 30 | 31 | load_composable( 32 | include_str!("bevy_pbr_wgsl/mesh_view_types.wgsl"), 33 | "examples/bevy_pbr_wgsl/mesh_view_types.wgsl", 34 | ); 35 | load_composable( 36 | include_str!("bevy_pbr_wgsl/mesh_view_bindings.wgsl"), 37 | "examples/bevy_pbr_wgsl/mesh_view_bindings.wgsl", 38 | ); 39 | 40 | load_composable( 41 | include_str!("bevy_pbr_wgsl/pbr_types.wgsl"), 42 | "examples/bevy_pbr_wgsl/pbr_types.wgsl", 43 | ); 44 | load_composable( 45 | include_str!("bevy_pbr_wgsl/pbr_bindings.wgsl"), 46 | "examples/bevy_pbr_wgsl/pbr_bindings.wgsl", 47 | ); 48 | 49 | load_composable( 50 | include_str!("bevy_pbr_wgsl/skinning.wgsl"), 51 | "examples/bevy_pbr_wgsl/skinning.wgsl", 52 | ); 53 | load_composable( 54 | include_str!("bevy_pbr_wgsl/mesh_types.wgsl"), 55 | "examples/bevy_pbr_wgsl/mesh_types.wgsl", 56 | ); 57 | load_composable( 58 | include_str!("bevy_pbr_wgsl/mesh_bindings.wgsl"), 59 | "examples/bevy_pbr_wgsl/mesh_bindings.wgsl", 60 | ); 61 | load_composable( 62 | include_str!("bevy_pbr_wgsl/mesh_vertex_output.wgsl"), 63 | "examples/bevy_pbr_wgsl/mesh_vertex_output.wgsl", 64 | ); 65 | 66 | load_composable( 67 | include_str!("bevy_pbr_wgsl/clustered_forward.wgsl"), 68 | "examples/bevy_pbr_wgsl/clustered_forward.wgsl", 69 | ); 70 | load_composable( 71 | include_str!("bevy_pbr_wgsl/pbr_lighting.wgsl"), 72 | "examples/bevy_pbr_wgsl/pbr_lighting.wgsl", 73 | ); 74 | load_composable( 75 | include_str!("bevy_pbr_wgsl/shadows.wgsl"), 76 | "examples/bevy_pbr_wgsl/shadows.wgsl", 77 | ); 78 | 79 | load_composable( 80 | include_str!("bevy_pbr_wgsl/pbr_functions.wgsl"), 81 | "examples/bevy_pbr_wgsl/pbr_functions.wgsl", 82 | ); 83 | 84 | composer 85 | } 86 | 87 | // rebuild composer every time 88 | fn test_compose_full() -> Result { 89 | let mut composer = init_composer(); 90 | 91 | match composer.make_naga_module(NagaModuleDescriptor { 92 | source: include_str!("bevy_pbr_wgsl/pbr.wgsl"), 93 | file_path: "examples/bevy_pbr_wgsl/pbr.wgsl", 94 | shader_defs: [("VERTEX_UVS".to_owned(), Default::default())].into(), 95 | ..Default::default() 96 | }) { 97 | Ok(module) => { 98 | // println!("shader: {:#?}", module); 99 | // let info = composer.create_validator().validate(&module).unwrap(); 100 | // let _wgsl = naga::back::wgsl::write_string(&module, &info, naga::back::wgsl::WriterFlags::EXPLICIT_TYPES).unwrap(); 101 | // println!("wgsl: \n\n{}", wgsl); 102 | Ok(module) 103 | } 104 | Err(e) => { 105 | println!("{}", e.emit_to_string(&composer)); 106 | Err(e) 107 | } 108 | } 109 | } 110 | 111 | // make naga module from initialized composer 112 | fn test_compose_final_module(n: usize, composer: &mut Composer) { 113 | let mut shader; 114 | for _ in 0..n { 115 | shader = match composer.make_naga_module(NagaModuleDescriptor { 116 | source: include_str!("bevy_pbr_wgsl/pbr.wgsl"), 117 | file_path: "examples/bevy_pbr_wgsl/pbr.wgsl", 118 | shader_defs: [("VERTEX_UVS".to_owned(), Default::default())].into(), 119 | ..Default::default() 120 | }) { 121 | Ok(module) => { 122 | // println!("shader: {:#?}", module); 123 | // let info = composer.create_validator().validate(&module).unwrap(); 124 | // let _wgsl = naga::back::wgsl::write_string(&module, &info, naga::back::wgsl::WriterFlags::EXPLICIT_TYPES).unwrap(); 125 | // println!("wgsl: \n\n{}", wgsl); 126 | Ok(module) 127 | } 128 | Err(e) => { 129 | println!("error: {e:#?}"); 130 | Err(e) 131 | } 132 | }; 133 | 134 | if shader.as_ref().unwrap().types.iter().next().is_none() { 135 | println!("ouch"); 136 | } 137 | } 138 | } 139 | 140 | // make shader module from string 141 | fn test_wgsl_string_compile(n: usize) { 142 | let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default()); 143 | let adapter = instance 144 | .enumerate_adapters(wgpu::Backends::all()) 145 | .into_iter() 146 | .next() 147 | .unwrap(); 148 | let device = 149 | futures_lite::future::block_on(adapter.request_device(&wgpu::DeviceDescriptor::default())) 150 | .unwrap() 151 | .0; 152 | 153 | for _ in 0..n { 154 | let _desc = device.create_shader_module(wgpu::ShaderModuleDescriptor { 155 | source: wgpu::ShaderSource::Wgsl( 156 | include_str!("bevy_pbr_wgsl/output_VERTEX_UVS.wgsl").into(), 157 | ), 158 | label: None, 159 | }); 160 | } 161 | } 162 | 163 | // make shader module from composed naga 164 | fn test_composer_compile(n: usize, composer: &mut Composer) { 165 | let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default()); 166 | let adapter = instance 167 | .enumerate_adapters(wgpu::Backends::all()) 168 | .into_iter() 169 | .next() 170 | .unwrap(); 171 | let device = 172 | futures_lite::future::block_on(adapter.request_device(&wgpu::DeviceDescriptor::default())) 173 | .unwrap() 174 | .0; 175 | 176 | for _ in 0..n { 177 | let module = composer 178 | .make_naga_module(NagaModuleDescriptor { 179 | source: include_str!("bevy_pbr_wgsl/pbr.wgsl"), 180 | file_path: "examples/bevy_pbr_wgsl/pbr.wgsl", 181 | shader_defs: [("VERTEX_UVS".to_owned(), Default::default())].into(), 182 | ..Default::default() 183 | }) 184 | .unwrap(); 185 | let _desc = device.create_shader_module(wgpu::ShaderModuleDescriptor { 186 | source: wgpu::ShaderSource::Naga(Cow::Owned(module)), 187 | label: None, 188 | }); 189 | } 190 | } 191 | 192 | fn main() { 193 | println!("running 1000 full composer builds (no caching)"); 194 | let start = std::time::Instant::now(); 195 | for _ in 0..1000 { 196 | let pbr = test_compose_full().unwrap(); 197 | if pbr.types.iter().next().is_none() { 198 | println!("ouch"); 199 | } 200 | } 201 | let end = std::time::Instant::now(); 202 | println!("1000 full builds: {:?}", end - start); 203 | 204 | let mut composer = init_composer(); 205 | 206 | println!("running 10000 composer final builds"); 207 | let start = std::time::Instant::now(); 208 | test_compose_final_module(10000, &mut composer); 209 | let end = std::time::Instant::now(); 210 | println!("10000 final builds: {:?}", end - start); 211 | 212 | println!("running 10000 wgpu string compiles"); 213 | let start = std::time::Instant::now(); 214 | test_wgsl_string_compile(10000); 215 | let end = std::time::Instant::now(); 216 | println!("10000 string compiles: {:?}", end - start); 217 | 218 | println!("running 10000 composer builds + wgpu module compiles"); 219 | let start = std::time::Instant::now(); 220 | test_composer_compile(10000, &mut composer); 221 | let end = std::time::Instant::now(); 222 | println!("10000 module compiles: {:?}", end - start); 223 | } 224 | -------------------------------------------------------------------------------- /src/compose/comment_strip_iter.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, str::Lines}; 2 | 3 | use regex::Regex; 4 | 5 | // outside of blocks and quotes, change state on //, /* or " 6 | static RE_NONE: once_cell::sync::Lazy = 7 | once_cell::sync::Lazy::new(|| Regex::new(r#"(//|/\*|\")"#).unwrap()); 8 | // in blocks, change on /* and */ 9 | static RE_BLOCK: once_cell::sync::Lazy = 10 | once_cell::sync::Lazy::new(|| Regex::new(r"(/\*|\*/)").unwrap()); 11 | // in quotes, change only on " 12 | static RE_QUOTE: once_cell::sync::Lazy = 13 | once_cell::sync::Lazy::new(|| Regex::new(r#"\""#).unwrap()); 14 | 15 | #[derive(PartialEq, Eq)] 16 | enum CommentState { 17 | None, 18 | Block(usize), 19 | Quote, 20 | } 21 | 22 | pub struct CommentReplaceIter<'a> { 23 | lines: &'a mut Lines<'a>, 24 | state: CommentState, 25 | } 26 | 27 | impl<'a> Iterator for CommentReplaceIter<'a> { 28 | type Item = Cow<'a, str>; 29 | 30 | fn next(&mut self) -> Option { 31 | let line_in = self.lines.next()?; 32 | 33 | // fast path 34 | if self.state == CommentState::None && !RE_NONE.is_match(line_in) { 35 | return Some(Cow::Borrowed(line_in)); 36 | } 37 | 38 | let mut output = String::new(); 39 | let mut section_start = 0; 40 | 41 | loop { 42 | let marker = match self.state { 43 | CommentState::None => &RE_NONE, 44 | CommentState::Block(_) => &RE_BLOCK, 45 | CommentState::Quote => &RE_QUOTE, 46 | } 47 | .find(&line_in[section_start..]); 48 | 49 | let section_end = marker 50 | .map(|m| section_start + m.start()) 51 | .unwrap_or(line_in.len()); 52 | 53 | if let CommentState::Block(_) = self.state { 54 | output.extend(std::iter::repeat_n(' ', section_end - section_start)); 55 | } else { 56 | output.push_str(&line_in[section_start..section_end]); 57 | } 58 | 59 | match marker { 60 | None => return Some(Cow::Owned(output)), 61 | Some(marker) => { 62 | match marker.as_str() { 63 | // only possible in None state 64 | "//" => { 65 | output.extend(std::iter::repeat_n( 66 | ' ', 67 | line_in.len() - marker.start() - section_start, 68 | )); 69 | return Some(Cow::Owned(output)); 70 | } 71 | // only possible in None or Block state 72 | "/*" => { 73 | self.state = match self.state { 74 | CommentState::None => CommentState::Block(1), 75 | CommentState::Block(n) => CommentState::Block(n + 1), 76 | _ => unreachable!(), 77 | }; 78 | output.push_str(" "); 79 | } 80 | // only possible in Block state 81 | "*/" => { 82 | self.state = match self.state { 83 | CommentState::Block(1) => CommentState::None, 84 | CommentState::Block(n) => CommentState::Block(n - 1), 85 | _ => unreachable!(), 86 | }; 87 | output.push_str(" "); 88 | } 89 | // only possible in None or Quote state 90 | "\"" => { 91 | self.state = match self.state { 92 | CommentState::None => CommentState::Quote, 93 | CommentState::Quote => CommentState::None, 94 | _ => unreachable!(), 95 | }; 96 | output.push('"'); 97 | } 98 | _ => unreachable!(), 99 | } 100 | section_start += marker.end(); 101 | } 102 | } 103 | } 104 | } 105 | } 106 | 107 | pub trait CommentReplaceExt<'a> { 108 | /// replace WGSL and GLSL comments with whitespace characters 109 | fn replace_comments(&'a mut self) -> CommentReplaceIter<'a>; 110 | } 111 | 112 | impl<'a> CommentReplaceExt<'a> for Lines<'a> { 113 | fn replace_comments(&'a mut self) -> CommentReplaceIter<'a> { 114 | CommentReplaceIter { 115 | lines: self, 116 | state: CommentState::None, 117 | } 118 | } 119 | } 120 | 121 | #[test] 122 | fn comment_test() { 123 | const INPUT: &str = r" 124 | not commented 125 | // line commented 126 | not commented 127 | /* block commented on a line */ 128 | not commented 129 | // line comment with a /* block comment unterminated 130 | not commented 131 | /* block comment 132 | spanning lines */ 133 | not commented 134 | /* block comment 135 | spanning lines and with // line comments 136 | even with a // line commented terminator */ 137 | not commented 138 | "; 139 | 140 | assert_eq!( 141 | INPUT 142 | .lines() 143 | .replace_comments() 144 | .zip(INPUT.lines()) 145 | .find(|(line, original)| { 146 | (line != "not commented" && !line.chars().all(|c| c == ' ')) 147 | || line.len() != original.len() 148 | }), 149 | None 150 | ); 151 | 152 | const PARTIAL_TESTS: [(&str, &str); 11] = [ 153 | ( 154 | "1.0 /* block comment with a partial line comment on the end *// 2.0", 155 | "1.0 / 2.0", 156 | ), 157 | ( 158 | "1.0 /* block comment with a partial block comment on the end */* 2.0", 159 | "1.0 * 2.0", 160 | ), 161 | ( 162 | "1.0 /* block comment 1 *//* block comment 2 */ * 2.0", 163 | "1.0 * 2.0", 164 | ), 165 | ( 166 | "1.0 /* block comment with real line comment after */// line comment", 167 | "1.0 ", 168 | ), 169 | ("*/", "*/"), 170 | ( 171 | r#"#import "embedded://file.wgsl""#, 172 | r#"#import "embedded://file.wgsl""#, 173 | ), 174 | ( 175 | r#"// #import "embedded://file.wgsl""#, 176 | r#" "#, 177 | ), 178 | ( 179 | r#"/* #import "embedded://file.wgsl" */"#, 180 | r#" "#, 181 | ), 182 | ( 183 | r#"/* #import "embedded:*/file.wgsl" */"#, 184 | r#" file.wgsl" */"#, 185 | ), 186 | ( 187 | r#"#import "embedded://file.wgsl" // comment"#, 188 | r#"#import "embedded://file.wgsl" "#, 189 | ), 190 | ( 191 | r#"#import "embedded:/* */ /* /**/* / / /// * / //*/*/ / */*file.wgsl""#, 192 | r#"#import "embedded:/* */ /* /**/* / / /// * / //*/*/ / */*file.wgsl""#, 193 | ), 194 | ]; 195 | 196 | for &(input, expected) in PARTIAL_TESTS.iter() { 197 | let mut nasty_processed = input.lines(); 198 | let nasty_processed = nasty_processed.replace_comments().next().unwrap(); 199 | assert_eq!(&nasty_processed, expected); 200 | } 201 | } 202 | 203 | #[test] 204 | fn multiline_comment_test() { 205 | let test_cases = [ 206 | ( 207 | // Basic test 208 | r"/* 209 | hoho 210 | */", 211 | r" 212 | 213 | ", 214 | ), 215 | ( 216 | // Testing the commenting-out of multiline comments 217 | r"///* 218 | hehe 219 | //*/", 220 | r" 221 | hehe 222 | ", 223 | ), 224 | ( 225 | // Testing the commenting-out of single-line comments 226 | r"/* // */ code goes here /* 227 | Still a comment // */ 228 | /* dummy */", 229 | r" code goes here 230 | 231 | ", 232 | ), 233 | ( 234 | // A comment with a nested multiline comment 235 | // Notice how the "//" inside the multiline comment doesn't take effect 236 | r"/* 237 | //* 238 | */commented 239 | */not commented", 240 | r" 241 | 242 | 243 | not commented", 244 | ), 245 | ]; 246 | 247 | for &(input, expected) in test_cases.iter() { 248 | for (output_line, expected_line) in input.lines().replace_comments().zip(expected.lines()) { 249 | assert_eq!(output_line.as_ref(), expected_line); 250 | } 251 | } 252 | } 253 | 254 | #[test] 255 | fn test_comment_becomes_spaces() { 256 | let test_cases = [("let a/**/b =3u;", "let a b =3u;")]; 257 | for &(input, expected) in test_cases.iter() { 258 | for (output_line, expected_line) in input.lines().replace_comments().zip(expected.lines()) { 259 | assert_eq!(output_line.as_ref(), expected_line); 260 | } 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/compose/error.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, collections::HashMap, ops::Range}; 2 | 3 | use codespan_reporting::{ 4 | diagnostic::{Diagnostic, Label}, 5 | files::SimpleFile, 6 | term, 7 | term::termcolor::WriteColor, 8 | }; 9 | use thiserror::Error; 10 | use tracing::trace; 11 | 12 | use super::{preprocess::PreprocessOutput, Composer, ShaderDefValue}; 13 | use crate::{compose::SPAN_SHIFT, redirect::RedirectError}; 14 | 15 | #[derive(Debug)] 16 | pub enum ErrSource { 17 | Module { 18 | name: String, 19 | offset: usize, 20 | defs: HashMap, 21 | }, 22 | Constructing { 23 | path: String, 24 | source: String, 25 | offset: usize, 26 | }, 27 | } 28 | 29 | impl ErrSource { 30 | pub fn path<'a>(&'a self, composer: &'a Composer) -> &'a String { 31 | match self { 32 | ErrSource::Module { name, .. } => &composer.module_sets.get(name).unwrap().file_path, 33 | ErrSource::Constructing { path, .. } => path, 34 | } 35 | } 36 | 37 | pub fn source<'a>(&'a self, composer: &'a Composer) -> Cow<'a, String> { 38 | match self { 39 | ErrSource::Module { name, defs, .. } => { 40 | let raw_source = &composer.module_sets.get(name).unwrap().sanitized_source; 41 | let Ok(PreprocessOutput { 42 | preprocessed_source: source, 43 | .. 44 | }) = composer.preprocessor.preprocess(raw_source, defs) 45 | else { 46 | return Default::default(); 47 | }; 48 | 49 | Cow::Owned(source) 50 | } 51 | ErrSource::Constructing { source, .. } => Cow::Borrowed(source), 52 | } 53 | } 54 | 55 | pub fn offset(&self) -> usize { 56 | match self { 57 | ErrSource::Module { offset, .. } | ErrSource::Constructing { offset, .. } => *offset, 58 | } 59 | } 60 | } 61 | 62 | #[derive(Debug, Error)] 63 | #[error("Composer error: {inner}")] 64 | pub struct ComposerError { 65 | #[source] 66 | pub inner: ComposerErrorInner, 67 | pub source: ErrSource, 68 | } 69 | 70 | #[derive(Debug, Error)] 71 | pub enum ComposerErrorInner { 72 | #[error("{0}")] 73 | ImportParseError(String, usize), 74 | #[error("required import '{0}' not found")] 75 | ImportNotFound(String, usize), 76 | #[error("{0}")] 77 | WgslParseError(naga::front::wgsl::ParseError), 78 | #[cfg(feature = "glsl")] 79 | #[error("{0:?}")] 80 | GlslParseError(naga::front::glsl::ParseErrors), 81 | #[error("naga_oil bug, please file a report: failed to convert imported module IR back into WGSL for use with WGSL shaders: {0}")] 82 | WgslBackError(naga::back::wgsl::Error), 83 | #[cfg(feature = "glsl")] 84 | #[error("naga_oil bug, please file a report: failed to convert imported module IR back into GLSL for use with GLSL shaders: {0}")] 85 | GlslBackError(naga::back::glsl::Error), 86 | #[error("naga_oil bug, please file a report: composer failed to build a valid header: {0}")] 87 | HeaderValidationError(naga::WithSpan), 88 | #[error("failed to build a valid final module: {0}")] 89 | ShaderValidationError(naga::WithSpan), 90 | #[error( 91 | "Not enough '# endif' lines. Each if statement should be followed by an endif statement." 92 | )] 93 | NotEnoughEndIfs(usize), 94 | #[error("Too many '# endif' lines. Each endif should be preceded by an if statement.")] 95 | TooManyEndIfs(usize), 96 | #[error("'#else' without preceding condition.")] 97 | ElseWithoutCondition(usize), 98 | #[error("Unknown shader def operator: '{operator}'")] 99 | UnknownShaderDefOperator { pos: usize, operator: String }, 100 | #[error("Unknown shader def: '{shader_def_name}'")] 101 | UnknownShaderDef { pos: usize, shader_def_name: String }, 102 | #[error( 103 | "Invalid shader def comparison for '{shader_def_name}': expected {expected}, got {value}" 104 | )] 105 | InvalidShaderDefComparisonValue { 106 | pos: usize, 107 | shader_def_name: String, 108 | expected: String, 109 | value: String, 110 | }, 111 | #[error("multiple inconsistent shader def values: '{def}'")] 112 | InconsistentShaderDefValue { def: String }, 113 | #[error("Attempted to add a module with no #define_import_path")] 114 | NoModuleName, 115 | #[error("source contains internal decoration string, results probably won't be what you expect. if you have a legitimate reason to do this please file a report")] 116 | DecorationInSource(Range), 117 | #[error("naga oil only supports glsl 440 and 450")] 118 | GlslInvalidVersion(usize), 119 | #[error("invalid override :{0}")] 120 | RedirectError(#[from] RedirectError), 121 | #[error( 122 | "override is invalid as `{name}` is not virtual (this error can be disabled with feature 'override_any')" 123 | )] 124 | OverrideNotVirtual { name: String, pos: usize }, 125 | #[error( 126 | "Composable module identifiers must not require substitution according to naga writeback rules: `{original}`" 127 | )] 128 | InvalidIdentifier { original: String, at: naga::Span }, 129 | #[error("Invalid value for `#define`d shader def {name}: {value}")] 130 | InvalidShaderDefDefinitionValue { 131 | name: String, 132 | value: String, 133 | pos: usize, 134 | }, 135 | #[error("#define statements are only allowed at the start of the top-level shaders")] 136 | DefineInModule(usize), 137 | } 138 | 139 | struct ErrorSources<'a> { 140 | current: Option<&'a (dyn std::error::Error + 'static)>, 141 | } 142 | 143 | impl<'a> ErrorSources<'a> { 144 | fn of(error: &'a dyn std::error::Error) -> Self { 145 | Self { 146 | current: error.source(), 147 | } 148 | } 149 | } 150 | 151 | impl<'a> Iterator for ErrorSources<'a> { 152 | type Item = &'a (dyn std::error::Error + 'static); 153 | 154 | fn next(&mut self) -> Option { 155 | let current = self.current; 156 | self.current = self.current.and_then(std::error::Error::source); 157 | current 158 | } 159 | } 160 | 161 | // impl<'a> FusedIterator for ErrorSources<'a> {} 162 | 163 | impl ComposerError { 164 | /// format a Composer error 165 | pub fn emit_to_string(&self, composer: &Composer) -> String { 166 | composer.undecorate(&self.emit_to_string_internal(composer)) 167 | } 168 | 169 | fn emit_to_string_internal(&self, composer: &Composer) -> String { 170 | let path = self.source.path(composer); 171 | let source = self.source.source(composer); 172 | let source_offset = self.source.offset(); 173 | 174 | trace!("source:\n~{}~", source); 175 | trace!("source offset: {}", source_offset); 176 | 177 | let map_span = |rng: Range| -> Range { 178 | ((rng.start & ((1 << SPAN_SHIFT) - 1)).saturating_sub(source_offset)) 179 | ..((rng.end & ((1 << SPAN_SHIFT) - 1)).saturating_sub(source_offset)) 180 | }; 181 | 182 | let files = SimpleFile::new(path, source.as_str()); 183 | let config = term::Config::default(); 184 | let (labels, notes) = match &self.inner { 185 | ComposerErrorInner::DecorationInSource(range) => { 186 | (vec![Label::primary((), range.clone())], vec![]) 187 | } 188 | ComposerErrorInner::HeaderValidationError(v) 189 | | ComposerErrorInner::ShaderValidationError(v) => ( 190 | v.spans() 191 | .map(|(span, desc)| { 192 | trace!( 193 | "mapping span {:?} -> {:?}", 194 | span.to_range().unwrap_or(0..0), 195 | map_span(span.to_range().unwrap_or(0..0)) 196 | ); 197 | Label::primary((), map_span(span.to_range().unwrap_or(0..0))) 198 | .with_message(desc.to_owned()) 199 | }) 200 | .collect(), 201 | ErrorSources::of(&v) 202 | .map(|source| source.to_string()) 203 | .collect(), 204 | ), 205 | ComposerErrorInner::ImportNotFound(msg, pos) => ( 206 | vec![Label::primary((), *pos..*pos)], 207 | vec![format!("missing import '{msg}'")], 208 | ), 209 | ComposerErrorInner::ImportParseError(msg, pos) => ( 210 | vec![Label::primary((), *pos..*pos)], 211 | vec![format!("invalid import spec: '{msg}'")], 212 | ), 213 | ComposerErrorInner::WgslParseError(e) => ( 214 | e.labels() 215 | .map(|(range, msg)| { 216 | Label::primary((), map_span(range.to_range().unwrap_or(0..0))) 217 | .with_message(msg) 218 | }) 219 | .collect(), 220 | vec![e.message().to_owned()], 221 | ), 222 | #[cfg(feature = "glsl")] 223 | ComposerErrorInner::GlslParseError(e) => ( 224 | e.errors 225 | .iter() 226 | .map(|naga::front::glsl::Error { kind, meta }| { 227 | Label::primary((), map_span(meta.to_range().unwrap_or(0..0))) 228 | .with_message(kind.to_string()) 229 | }) 230 | .collect(), 231 | vec![], 232 | ), 233 | ComposerErrorInner::NotEnoughEndIfs(pos) 234 | | ComposerErrorInner::TooManyEndIfs(pos) 235 | | ComposerErrorInner::ElseWithoutCondition(pos) 236 | | ComposerErrorInner::UnknownShaderDef { pos, .. } 237 | | ComposerErrorInner::UnknownShaderDefOperator { pos, .. } 238 | | ComposerErrorInner::InvalidShaderDefComparisonValue { pos, .. } 239 | | ComposerErrorInner::OverrideNotVirtual { pos, .. } 240 | | ComposerErrorInner::GlslInvalidVersion(pos) 241 | | ComposerErrorInner::DefineInModule(pos) 242 | | ComposerErrorInner::InvalidShaderDefDefinitionValue { pos, .. } => { 243 | (vec![Label::primary((), *pos..*pos)], vec![]) 244 | } 245 | ComposerErrorInner::WgslBackError(e) => { 246 | return format!("{path}: wgsl back error: {e}"); 247 | } 248 | #[cfg(feature = "glsl")] 249 | ComposerErrorInner::GlslBackError(e) => { 250 | return format!("{path}: glsl back error: {e}"); 251 | } 252 | ComposerErrorInner::InconsistentShaderDefValue { def } => { 253 | return format!("{path}: multiple inconsistent shader def values: '{def}'"); 254 | } 255 | ComposerErrorInner::RedirectError(..) => ( 256 | vec![Label::primary((), 0..0)], 257 | vec![format!("override error")], 258 | ), 259 | ComposerErrorInner::NoModuleName => { 260 | return format!( 261 | "{path}: no #define_import_path declaration found in composable module" 262 | ); 263 | } 264 | ComposerErrorInner::InvalidIdentifier { at, .. } => ( 265 | vec![Label::primary((), map_span(at.to_range().unwrap_or(0..0))) 266 | .with_message(self.inner.to_string())], 267 | vec![], 268 | ), 269 | }; 270 | 271 | let diagnostic = Diagnostic::error() 272 | .with_message(self.inner.to_string()) 273 | .with_labels(labels) 274 | .with_notes(notes); 275 | 276 | let mut msg = Vec::with_capacity(256); 277 | 278 | let mut color_writer; 279 | let mut no_color_writer; 280 | let writer: &mut dyn WriteColor = if supports_color() { 281 | color_writer = term::termcolor::Ansi::new(&mut msg); 282 | &mut color_writer 283 | } else { 284 | no_color_writer = term::termcolor::NoColor::new(&mut msg); 285 | &mut no_color_writer 286 | }; 287 | 288 | term::emit(writer, &config, &files, &diagnostic).expect("cannot write error"); 289 | 290 | String::from_utf8_lossy(&msg).into_owned() 291 | } 292 | } 293 | 294 | #[cfg(any(test, target_arch = "wasm32"))] 295 | fn supports_color() -> bool { 296 | false 297 | } 298 | 299 | // termcolor doesn't expose this logic when using custom buffers 300 | #[cfg(not(any(test, target_arch = "wasm32")))] 301 | fn supports_color() -> bool { 302 | match std::env::var_os("TERM") { 303 | None if cfg!(unix) => false, 304 | Some(term) if term == "dumb" => false, 305 | _ => std::env::var_os("NO_COLOR").is_none(), 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /src/compose/parse_imports.rs: -------------------------------------------------------------------------------- 1 | use indexmap::IndexMap; 2 | 3 | use super::{ 4 | tokenizer::{Token, Tokenizer}, 5 | Composer, ImportDefWithOffset, ImportDefinition, 6 | }; 7 | 8 | pub fn parse_imports<'a>( 9 | input: &'a str, 10 | declared_imports: &mut IndexMap>, 11 | ) -> Result<(), (&'a str, usize)> { 12 | let mut tokens = Tokenizer::new(input, false).peekable(); 13 | 14 | match tokens.next() { 15 | Some(Token::Other('#', _)) => (), 16 | Some(other) => return Err(("expected `#import`", other.pos())), 17 | None => return Err(("expected #import", input.len())), 18 | }; 19 | match tokens.next() { 20 | Some(Token::Identifier("import", _)) => (), 21 | Some(other) => return Err(("expected `#import`", other.pos())), 22 | None => return Err(("expected `#import`", input.len())), 23 | }; 24 | 25 | let mut stack = Vec::default(); 26 | let mut current = String::default(); 27 | let mut as_name = None; 28 | let mut is_deprecated_itemlist = false; 29 | 30 | loop { 31 | match tokens.peek() { 32 | Some(Token::Identifier(ident, _)) => { 33 | current.push_str(ident); 34 | tokens.next(); 35 | 36 | if tokens.peek().and_then(Token::identifier) == Some("as") { 37 | let pos = tokens.next().unwrap().pos(); 38 | let Some(Token::Identifier(name, _)) = tokens.next() else { 39 | return Err(("expected identifier after `as`", pos)); 40 | }; 41 | 42 | as_name = Some(name); 43 | } 44 | 45 | // support deprecated #import mod item 46 | if let Some(Token::Identifier(..)) = tokens.peek() { 47 | #[cfg(not(feature = "allow_deprecated"))] 48 | tracing::warn!("item list imports are deprecated, please use `rust::style::item_imports` (or use feature `allow_deprecated`)`\n| {}", input); 49 | 50 | is_deprecated_itemlist = true; 51 | stack.push(format!("{}::", current)); 52 | current = String::default(); 53 | as_name = None; 54 | } 55 | 56 | continue; 57 | } 58 | Some(Token::Other('{', pos)) => { 59 | if !current.ends_with("::") { 60 | return Err(("open brace must follow `::`", *pos)); 61 | } 62 | stack.push(current); 63 | current = String::default(); 64 | as_name = None; 65 | } 66 | Some(Token::Other(',', _)) 67 | | Some(Token::Other('}', _)) 68 | | Some(Token::Other('\n', _)) 69 | | None => { 70 | if !current.is_empty() { 71 | let used_name = as_name.map(ToString::to_string).unwrap_or_else(|| { 72 | current 73 | .rsplit_once("::") 74 | .map(|(_, name)| name.to_owned()) 75 | .unwrap_or(current.clone()) 76 | }); 77 | declared_imports.entry(used_name).or_default().push(format!( 78 | "{}{}", 79 | stack.join(""), 80 | current 81 | )); 82 | current = String::default(); 83 | as_name = None; 84 | } 85 | 86 | if let Some(Token::Other('}', pos)) = tokens.peek() { 87 | if stack.pop().is_none() { 88 | return Err(("close brace without open", *pos)); 89 | } 90 | } 91 | 92 | if tokens.peek().is_none() { 93 | break; 94 | } 95 | } 96 | Some(Token::Other(';', _)) => { 97 | tokens.next(); 98 | if let Some(token) = tokens.peek() { 99 | return Err(("unexpected token after ';'", token.pos())); 100 | } 101 | } 102 | Some(Token::Other(_, pos)) => return Err(("unexpected token", *pos)), 103 | Some(Token::Whitespace(..)) => unreachable!(), 104 | } 105 | 106 | tokens.next(); 107 | } 108 | 109 | if !(stack.is_empty() || is_deprecated_itemlist && stack.len() == 1) { 110 | return Err(("missing close brace", input.len())); 111 | } 112 | 113 | Ok(()) 114 | } 115 | 116 | pub fn substitute_identifiers( 117 | input: &str, 118 | offset: usize, 119 | declared_imports: &IndexMap>, 120 | used_imports: &mut IndexMap, 121 | allow_ambiguous: bool, 122 | ) -> Result { 123 | let tokens = Tokenizer::new(input, true); 124 | let mut output = String::with_capacity(input.len()); 125 | let mut in_substitution_position = true; 126 | 127 | for token in tokens { 128 | match token { 129 | Token::Identifier(ident, token_pos) => { 130 | if in_substitution_position { 131 | let (first, residual) = ident.split_once("::").unwrap_or((ident, "")); 132 | let full_paths = declared_imports 133 | .get(first) 134 | .cloned() 135 | .unwrap_or(vec![first.to_owned()]); 136 | 137 | if !allow_ambiguous && full_paths.len() > 1 { 138 | return Err(offset + token_pos); 139 | } 140 | 141 | for mut full_path in full_paths { 142 | if !residual.is_empty() { 143 | full_path.push_str("::"); 144 | full_path.push_str(residual); 145 | } 146 | 147 | if let Some((module, item)) = full_path.rsplit_once("::") { 148 | used_imports 149 | .entry(module.to_owned()) 150 | .or_insert_with(|| ImportDefWithOffset { 151 | definition: ImportDefinition { 152 | import: module.to_owned(), 153 | ..Default::default() 154 | }, 155 | offset: offset + token_pos, 156 | }) 157 | .definition 158 | .items 159 | .push(item.to_owned()); 160 | output.push_str(item); 161 | output.push_str(&Composer::decorate(module)); 162 | } else if full_path.find('"').is_some() { 163 | // we don't want to replace local variables that shadow quoted module imports with the 164 | // quoted name as that won't compile. 165 | // since quoted items always refer to modules, we can just emit the original ident 166 | // in this case 167 | output.push_str(ident); 168 | } else { 169 | // if there are no quotes we do the replacement. this means that individually imported 170 | // items can be used, and any shadowing local variables get harmlessly renamed. 171 | // TODO: it can lead to weird errors, but such is life 172 | output.push_str(&full_path); 173 | } 174 | } 175 | } else { 176 | output.push_str(ident); 177 | } 178 | } 179 | Token::Other(other, _) => { 180 | output.push(other); 181 | if other == '.' || other == '@' { 182 | in_substitution_position = false; 183 | continue; 184 | } 185 | } 186 | Token::Whitespace(ws, _) => output.push_str(ws), 187 | } 188 | 189 | in_substitution_position = true; 190 | } 191 | 192 | Ok(output) 193 | } 194 | 195 | #[cfg(test)] 196 | fn test_parse(input: &str) -> Result>, (&str, usize)> { 197 | let mut declared_imports = IndexMap::default(); 198 | parse_imports(input, &mut declared_imports)?; 199 | Ok(declared_imports) 200 | } 201 | 202 | #[test] 203 | fn import_tokens() { 204 | let input = r" 205 | #import a::b 206 | "; 207 | assert_eq!( 208 | test_parse(input), 209 | Ok(IndexMap::from_iter([( 210 | "b".to_owned(), 211 | vec!("a::b".to_owned()) 212 | )])) 213 | ); 214 | 215 | let input = r" 216 | #import a::{b, c} 217 | "; 218 | assert_eq!( 219 | test_parse(input), 220 | Ok(IndexMap::from_iter([ 221 | ("b".to_owned(), vec!("a::b".to_owned())), 222 | ("c".to_owned(), vec!("a::c".to_owned())), 223 | ])) 224 | ); 225 | 226 | let input = r" 227 | #import a::{b as d, c} 228 | "; 229 | assert_eq!( 230 | test_parse(input), 231 | Ok(IndexMap::from_iter([ 232 | ("d".to_owned(), vec!("a::b".to_owned())), 233 | ("c".to_owned(), vec!("a::c".to_owned())), 234 | ])) 235 | ); 236 | 237 | let input = r" 238 | #import a::{b::{c, d}, e} 239 | "; 240 | assert_eq!( 241 | test_parse(input), 242 | Ok(IndexMap::from_iter([ 243 | ("c".to_owned(), vec!("a::b::c".to_owned())), 244 | ("d".to_owned(), vec!("a::b::d".to_owned())), 245 | ("e".to_owned(), vec!("a::e".to_owned())), 246 | ])) 247 | ); 248 | 249 | let input = r" 250 | #import a::b::{c, d}, e 251 | "; 252 | assert_eq!( 253 | test_parse(input), 254 | Ok(IndexMap::from_iter([ 255 | ("c".to_owned(), vec!("a::b::c".to_owned())), 256 | ("d".to_owned(), vec!("a::b::d".to_owned())), 257 | ("e".to_owned(), vec!("e".to_owned())), 258 | ])) 259 | ); 260 | 261 | let input = r" 262 | #import a, b 263 | "; 264 | assert_eq!( 265 | test_parse(input), 266 | Ok(IndexMap::from_iter([ 267 | ("a".to_owned(), vec!("a".to_owned())), 268 | ("b".to_owned(), vec!("b".to_owned())), 269 | ])) 270 | ); 271 | 272 | let input = r" 273 | #import a::b c, d 274 | "; 275 | assert_eq!( 276 | test_parse(input), 277 | Ok(IndexMap::from_iter([ 278 | ("c".to_owned(), vec!("a::b::c".to_owned())), 279 | ("d".to_owned(), vec!("a::b::d".to_owned())), 280 | ])) 281 | ); 282 | 283 | let input = r" 284 | #import a::b c 285 | "; 286 | assert_eq!( 287 | test_parse(input), 288 | Ok(IndexMap::from_iter([( 289 | "c".to_owned(), 290 | vec!("a::b::c".to_owned()) 291 | ),])) 292 | ); 293 | 294 | let input = r" 295 | #import a::b::{c::{d, e}, f, g::{h as i, j}} 296 | "; 297 | assert_eq!( 298 | test_parse(input), 299 | Ok(IndexMap::from_iter([ 300 | ("d".to_owned(), vec!("a::b::c::d".to_owned())), 301 | ("e".to_owned(), vec!("a::b::c::e".to_owned())), 302 | ("f".to_owned(), vec!("a::b::f".to_owned())), 303 | ("i".to_owned(), vec!("a::b::g::h".to_owned())), 304 | ("j".to_owned(), vec!("a::b::g::j".to_owned())), 305 | ])) 306 | ); 307 | 308 | let input = r" 309 | #import a::b::{ 310 | c::{d, e}, 311 | f, 312 | g::{ 313 | h as i, 314 | j::k::l as m, 315 | } 316 | } 317 | "; 318 | assert_eq!( 319 | test_parse(input), 320 | Ok(IndexMap::from_iter([ 321 | ("d".to_owned(), vec!("a::b::c::d".to_owned())), 322 | ("e".to_owned(), vec!("a::b::c::e".to_owned())), 323 | ("f".to_owned(), vec!("a::b::f".to_owned())), 324 | ("i".to_owned(), vec!("a::b::g::h".to_owned())), 325 | ("m".to_owned(), vec!("a::b::g::j::k::l".to_owned())), 326 | ])) 327 | ); 328 | 329 | let input = r#" 330 | #import "path//with\ all sorts of .stuff"::{a, b} 331 | "#; 332 | assert_eq!( 333 | test_parse(input), 334 | Ok(IndexMap::from_iter([ 335 | ( 336 | "a".to_owned(), 337 | vec!(r#""path//with\ all sorts of .stuff"::a"#.to_owned()) 338 | ), 339 | ( 340 | "b".to_owned(), 341 | vec!(r#""path//with\ all sorts of .stuff"::b"#.to_owned()) 342 | ), 343 | ])) 344 | ); 345 | 346 | let input = r" 347 | #import a::b::{ 348 | "; 349 | assert!(test_parse(input).is_err()); 350 | 351 | let input = r" 352 | #import a::b::{{c} 353 | "; 354 | assert!(test_parse(input).is_err()); 355 | 356 | let input = r" 357 | #import a::b::{c}} 358 | "; 359 | assert!(test_parse(input).is_err()); 360 | 361 | let input = r" 362 | #import a::b{{c,d}} 363 | "; 364 | assert!(test_parse(input).is_err()); 365 | 366 | let input = r" 367 | #import a:b 368 | "; 369 | assert!(test_parse(input).is_err()); 370 | } 371 | -------------------------------------------------------------------------------- /src/compose/tests/add_imports/overridable.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path overridable 2 | 3 | virtual fn func() -> f32 { 4 | return 1.0; 5 | } -------------------------------------------------------------------------------- /src/compose/tests/add_imports/plugin.wgsl: -------------------------------------------------------------------------------- 1 | #import overridable 2 | 3 | override fn overridable::func() -> f32 { 4 | return overridable::func() + 1.0; 5 | } -------------------------------------------------------------------------------- /src/compose/tests/add_imports/top.wgsl: -------------------------------------------------------------------------------- 1 | #import overridable 2 | 3 | fn entry_point() -> f32 { 4 | return overridable::func(); 5 | } -------------------------------------------------------------------------------- /src/compose/tests/atomics/mod.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path test_module 2 | 3 | var atom: atomic; 4 | 5 | fn entry_point() -> f32 { 6 | atomicStore(&atom, 1u); // atom = 1 7 | var y = atomicLoad(&atom); // y = 1, atom = 1 8 | y += atomicAdd(&atom, 2u); // y = 2, atom = 3 9 | y += atomicSub(&atom, 1u); // y = 5, atom = 2 10 | y += atomicMax(&atom, 5u); // y = 7, atom = 5 11 | y += atomicMin(&atom, 4u); // y = 12, atom = 4 12 | y += atomicExchange(&atom, y); // y = 16, atom = 12 13 | let exchange = atomicCompareExchangeWeak(&atom, 12u, 0u); 14 | if exchange.exchanged { 15 | y += exchange.old_value; // y = 28, atom = 0 16 | } 17 | 18 | return f32(y); // 28.0 19 | } -------------------------------------------------------------------------------- /src/compose/tests/atomics/top.wgsl: -------------------------------------------------------------------------------- 1 | #import test_module 2 | 3 | fn main() -> f32 { 4 | return test_module::entry_point(); 5 | } 6 | -------------------------------------------------------------------------------- /src/compose/tests/bevy_path_imports/skill.wgsl: -------------------------------------------------------------------------------- 1 | #import "shaders/skills/shared.wgsl" Vertex, VertexOutput 2 | 3 | #if EFFECT_ID == 0 4 | #import "shaders/skills/sound.wgsl" frag, vert 5 | #else if EFFECT_ID == 1 6 | #import "shaders/skills/orb.wgsl" frag, vert 7 | #else if EFFECT_ID == 2 8 | #import "shaders/skills/slash.wgsl" frag, vert 9 | #else if EFFECT_ID == 3 10 | #import "shaders/skills/railgun_trail.wgsl" frag, vert 11 | #else if EFFECT_ID == 4 12 | #import "shaders/skills/magic_arrow.wgsl" frag, vert 13 | #else if EFFECT_ID == 5 14 | #import "shaders/skills/hit.wgsl" frag, vert 15 | #else if EFFECT_ID == 6 16 | #import "shaders/skills/lightning_ring.wgsl" frag, vert 17 | #else if EFFECT_ID == 7 18 | #import "shaders/skills/lightning.wgsl" frag, vert 19 | #endif 20 | 21 | #import something_unused 22 | 23 | @fragment 24 | fn fragment(in: VertexOutput) -> @location(0) vec4 { 25 | return frag(in); 26 | } 27 | 28 | @vertex 29 | fn vertex(vertex: Vertex) -> VertexOutput { 30 | return vert(vertex); 31 | } 32 | -------------------------------------------------------------------------------- /src/compose/tests/big_shaderdefs/mod.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path mod 2 | 3 | fn f() -> f32 { 4 | var x = 0.0; 5 | #ifdef a1 6 | #ifdef a2 7 | #ifdef a3 8 | #ifdef a4 9 | #ifdef a5 10 | #ifdef a6 11 | #ifdef a7 12 | #ifdef a8 13 | #ifdef a9 14 | #ifdef a10 15 | #ifdef a11 16 | #ifdef a12 17 | #ifdef a13 18 | #ifdef a14 19 | #ifdef a15 20 | #ifdef a16 21 | #ifdef a17 22 | #ifdef a18 23 | #ifdef a19 24 | #ifdef a20 25 | #ifdef a21 26 | #ifdef a22 27 | #ifdef a23 28 | #ifdef a24 29 | #ifdef a25 30 | #ifdef a26 31 | #ifdef a27 32 | #ifdef a28 33 | #ifdef a29 34 | #ifdef a30 35 | #ifdef a31 36 | #ifdef a32 37 | #ifdef a33 38 | #ifdef a34 39 | #ifdef a35 40 | #ifdef a36 41 | #ifdef a37 42 | #ifdef a38 43 | #ifdef a39 44 | #ifdef a40 45 | #ifdef a41 46 | #ifdef a42 47 | #ifdef a43 48 | #ifdef a44 49 | #ifdef a45 50 | #ifdef a46 51 | #ifdef a47 52 | #ifdef a48 53 | #ifdef a49 54 | #ifdef a50 55 | #ifdef a51 56 | #ifdef a52 57 | #ifdef a53 58 | #ifdef a54 59 | #ifdef a55 60 | #ifdef a56 61 | #ifdef a57 62 | #ifdef a58 63 | #ifdef a59 64 | #ifdef a60 65 | #ifdef a61 66 | #ifdef a62 67 | #ifdef a63 68 | #ifdef a64 69 | #ifdef a65 70 | #ifdef a66 71 | #ifdef a66 72 | #ifdef a67 73 | x = 1.0; 74 | #endif 75 | #endif 76 | #endif 77 | #endif 78 | #endif 79 | #endif 80 | #endif 81 | #endif 82 | #endif 83 | #endif 84 | #endif 85 | #endif 86 | #endif 87 | #endif 88 | #endif 89 | #endif 90 | #endif 91 | #endif 92 | #endif 93 | #endif 94 | #endif 95 | #endif 96 | #endif 97 | #endif 98 | #endif 99 | #endif 100 | #endif 101 | #endif 102 | #endif 103 | #endif 104 | #endif 105 | #endif 106 | #endif 107 | #endif 108 | #endif 109 | #endif 110 | #endif 111 | #endif 112 | #endif 113 | #endif 114 | #endif 115 | #endif 116 | #endif 117 | #endif 118 | #endif 119 | #endif 120 | #endif 121 | #endif 122 | #endif 123 | #endif 124 | #endif 125 | #endif 126 | #endif 127 | #endif 128 | #endif 129 | #endif 130 | #endif 131 | #endif 132 | #endif 133 | #endif 134 | #endif 135 | #endif 136 | #endif 137 | #endif 138 | #endif 139 | #endif 140 | #endif 141 | #endif 142 | return x; 143 | } 144 | 145 | -------------------------------------------------------------------------------- /src/compose/tests/big_shaderdefs/top.wgsl: -------------------------------------------------------------------------------- 1 | #import mod 2 | 3 | fn main() -> f32 { 4 | return mod::f(); 5 | } -------------------------------------------------------------------------------- /src/compose/tests/call_entrypoint/include.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path include 2 | 3 | fn non_ep(f: f32) -> f32 { 4 | return f * 2.0; 5 | } 6 | 7 | @fragment 8 | fn fragment( 9 | @builtin(position) frag_coord: vec4, 10 | ) -> @location(0) vec4 { 11 | return vec4(1.5 * frag_coord); 12 | } -------------------------------------------------------------------------------- /src/compose/tests/call_entrypoint/top.wgsl: -------------------------------------------------------------------------------- 1 | #import include as Inc 2 | 3 | @fragment 4 | fn fragment( 5 | @builtin(position) frag_coord: vec4, 6 | ) -> @location(0) vec4 { 7 | return Inc::fragment(frag_coord); 8 | } -------------------------------------------------------------------------------- /src/compose/tests/compute_test.wgsl: -------------------------------------------------------------------------------- 1 | #import test_module 2 | 3 | @group(0) @binding(0) 4 | var buffer: f32; 5 | 6 | @compute @workgroup_size(1, 1, 1) 7 | fn run_test() { 8 | let res = test_module::entry_point(); 9 | buffer = res; 10 | } 11 | -------------------------------------------------------------------------------- /src/compose/tests/conditional_import/mod_a.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path a 2 | 3 | const C: u32 = 1u; -------------------------------------------------------------------------------- /src/compose/tests/conditional_import/mod_b.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path b 2 | 3 | const C: u32 = 2u; -------------------------------------------------------------------------------- /src/compose/tests/conditional_import/top.wgsl: -------------------------------------------------------------------------------- 1 | #ifdef USE_A 2 | #import a C 3 | #else 4 | #import b C 5 | #endif 6 | 7 | fn main() -> u32 { 8 | return C; 9 | } -------------------------------------------------------------------------------- /src/compose/tests/conditional_import_fail/middle.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path middle 2 | 3 | #ifdef USE_A 4 | #import a::b 5 | #endif 6 | 7 | fn mid_fn() -> u32 { 8 | return b::C; 9 | } 10 | -------------------------------------------------------------------------------- /src/compose/tests/conditional_import_fail/mod_a_b.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path a::b 2 | 3 | const C: u32 = 1u; 4 | -------------------------------------------------------------------------------- /src/compose/tests/conditional_import_fail/top.wgsl: -------------------------------------------------------------------------------- 1 | #ifdef USE_A 2 | #import a::b 3 | #endif 4 | 5 | fn main() -> u32 { 6 | return b::C; 7 | } 8 | -------------------------------------------------------------------------------- /src/compose/tests/conditional_import_fail/top_nested.wgsl: -------------------------------------------------------------------------------- 1 | #import middle 2 | 3 | fn main() -> u32 { 4 | return middle::mid_fn(); 5 | } 6 | -------------------------------------------------------------------------------- /src/compose/tests/const_in_decl/bind.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path bind 2 | 3 | #import consts 4 | 5 | const y: u32 = 2u; 6 | 7 | var arr: array; -------------------------------------------------------------------------------- /src/compose/tests/const_in_decl/consts.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path consts 2 | 3 | const X: u32 = 1u; -------------------------------------------------------------------------------- /src/compose/tests/const_in_decl/top.wgsl: -------------------------------------------------------------------------------- 1 | #import consts 2 | #import bind 3 | 4 | fn main() -> f32 { 5 | return f32(bind::arr[0]); 6 | } -------------------------------------------------------------------------------- /src/compose/tests/diagnostic_filters/filters.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path filters 2 | 3 | diagnostic(warning, derivative_uniformity); 4 | 5 | fn diagnostic_test(s : sampler, tex : texture_2d, ro_buffer : array) -> vec4f { 6 | if ro_buffer[0] == 0 { 7 | // Emits a derivative uniformity error during validation. 8 | return textureSample(tex, s, vec2(0.,0.)); 9 | } 10 | 11 | return vec4f(0.); 12 | } 13 | -------------------------------------------------------------------------------- /src/compose/tests/diagnostic_filters/top.wgsl: -------------------------------------------------------------------------------- 1 | #import filters 2 | 3 | @group(0) @binding(0) var s : sampler; 4 | @group(0) @binding(2) var tex : texture_2d; 5 | @group(1) @binding(0) var ro_buffer : array; 6 | 7 | @fragment 8 | fn main(@builtin(position) p : vec4f) -> @location(0) vec4f { 9 | return filters::diagnostic_test(); 10 | } 11 | -------------------------------------------------------------------------------- /src/compose/tests/dup_import/a.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path a 2 | 3 | #import consts 4 | 5 | fn f() -> f32 { 6 | return consts::PI * 1.0; 7 | } -------------------------------------------------------------------------------- /src/compose/tests/dup_import/all.wgsl: -------------------------------------------------------------------------------- 1 | const PI: f32 = 3.1; 2 | 3 | fn b__f() -> f32 { 4 | return PI * 2.0; 5 | } 6 | 7 | fn b__g() -> f32 { 8 | return PI * 2.0; 9 | } 10 | 11 | fn a__f() -> f32 { 12 | return PI * 1.0; 13 | } 14 | 15 | fn main() -> f32 { 16 | let x = a__f(); 17 | let y = b__f(); 18 | 19 | return x*y; 20 | } -------------------------------------------------------------------------------- /src/compose/tests/dup_import/b.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path b 2 | 3 | #import consts 4 | 5 | fn f() -> f32 { 6 | return consts::PI * 2.0; 7 | } 8 | 9 | fn g() -> f32 { 10 | return consts::PI * 2.0; 11 | } -------------------------------------------------------------------------------- /src/compose/tests/dup_import/consts.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path consts 2 | 3 | const PI: f32 = 3.1; -------------------------------------------------------------------------------- /src/compose/tests/dup_import/top.wgsl: -------------------------------------------------------------------------------- 1 | #import a 2 | #import b 3 | 4 | fn main() -> f32 { 5 | let x = a::f(); 6 | let y = b::f(); 7 | 8 | return x*y; 9 | } -------------------------------------------------------------------------------- /src/compose/tests/dup_struct_import/a.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path a 2 | #import struct 3 | 4 | fn a() -> struct::MyStruct { 5 | var s_a: struct::MyStruct; 6 | s_a.value = 1.0; 7 | return s_a; 8 | } -------------------------------------------------------------------------------- /src/compose/tests/dup_struct_import/b.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path b 2 | #import struct 3 | 4 | fn b() -> struct::MyStruct { 5 | var s_b: struct::MyStruct; 6 | s_b.value = 2.0; 7 | return s_b; 8 | } -------------------------------------------------------------------------------- /src/compose/tests/dup_struct_import/struct.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path struct 2 | 3 | struct MyStruct { 4 | value: f32, 5 | } -------------------------------------------------------------------------------- /src/compose/tests/dup_struct_import/top.wgsl: -------------------------------------------------------------------------------- 1 | #import a 2 | #import b 3 | 4 | fn main() -> f32 { 5 | let a = a::a(); 6 | let b = b::b(); 7 | return a.value / b.value; 8 | } -------------------------------------------------------------------------------- /src/compose/tests/effective_defs/mod.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path mod 2 | 3 | #ifdef DEF_ONE 4 | const a: u32 = 1u; 5 | #else 6 | const a: u32 = 0u; 7 | #endif 8 | 9 | #ifndef DEF_TWO 10 | const b: u32 = 0u; 11 | #else 12 | const b: u32 = 2u; 13 | #endif 14 | 15 | #if DEF_THREE == true 16 | const c: u32 = 4u; 17 | #else 18 | const c: u32 = 0u; 19 | #endif -------------------------------------------------------------------------------- /src/compose/tests/effective_defs/top.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path test_module 2 | #import mod a, b, c 3 | 4 | fn entry_point() -> f32 { 5 | return f32(a + b + c); 6 | } -------------------------------------------------------------------------------- /src/compose/tests/error_test/include.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path include 2 | 3 | doesn't matter what goes here for this test 4 | 5 | or here, just moving lines around a bit 6 | 7 | #import missing 8 | 9 | fn sub() { 10 | // have to use something for it to be declared missing 11 | let x = missing::y(); 12 | } 13 | -------------------------------------------------------------------------------- /src/compose/tests/error_test/wgsl_parse_err.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path wgsl_parse_err 2 | 3 | const VAL: u32 = 1u; 4 | 5 | fn all_ok() -> f32 { 6 | let x = 1.0; 7 | var y = sqrt(x); 8 | y += 1.0; 9 | return y; 10 | } 11 | 12 | fn woops() -> f32 { 13 | let x = 1.0; 14 | var y = sqrt(x); 15 | y += 1.0; 16 | return zdd; 17 | } -------------------------------------------------------------------------------- /src/compose/tests/error_test/wgsl_parse_wrap.wgsl: -------------------------------------------------------------------------------- 1 | fn ok() { 2 | wgsl_parse_err::woops(); 3 | } -------------------------------------------------------------------------------- /src/compose/tests/error_test/wgsl_valid_err.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path valid_inc 2 | 3 | fn ok() -> f32 { 4 | return 1.0; 5 | } 6 | 7 | fn func() -> f32 { 8 | return 1u; 9 | } 10 | 11 | fn still_ok() -> f32 { 12 | return 1.0; 13 | } 14 | 15 | fn main() { 16 | let x: f32 = func(); 17 | } -------------------------------------------------------------------------------- /src/compose/tests/error_test/wgsl_valid_wrap.wgsl: -------------------------------------------------------------------------------- 1 | fn whatever() { 2 | valid_inc::main(); 3 | } -------------------------------------------------------------------------------- /src/compose/tests/expected/additional_import.txt: -------------------------------------------------------------------------------- 1 | fn funcX_naga_oil_mod_XN53GK4TSNFSGCYTMMUX() -> f32 { 2 | return 1f; 3 | } 4 | 5 | fn funcX_naga_oil_vrt_XN53GK4TSNFSGCYTMMUXX_naga_oil_mod_XOBWHKZ3JNYX() -> f32 { 6 | let _e0: f32 = funcX_naga_oil_mod_XN53GK4TSNFSGCYTMMUX(); 7 | return (_e0 + 1f); 8 | } 9 | 10 | fn entry_point() -> f32 { 11 | let _e0: f32 = funcX_naga_oil_vrt_XN53GK4TSNFSGCYTMMUXX_naga_oil_mod_XOBWHKZ3JNYX(); 12 | return _e0; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/compose/tests/expected/atomics.txt: -------------------------------------------------------------------------------- 1 | var atomX_naga_oil_mod_XORSXG5C7NVXWI5LMMUX: atomic; 2 | 3 | fn entry_pointX_naga_oil_mod_XORSXG5C7NVXWI5LMMUX() -> f32 { 4 | var y: u32; 5 | 6 | atomicStore((&atomX_naga_oil_mod_XORSXG5C7NVXWI5LMMUX), 1u); 7 | let _e3: u32 = atomicLoad((&atomX_naga_oil_mod_XORSXG5C7NVXWI5LMMUX)); 8 | y = _e3; 9 | let _e7: u32 = atomicAdd((&atomX_naga_oil_mod_XORSXG5C7NVXWI5LMMUX), 2u); 10 | let _e8: u32 = y; 11 | y = (_e8 + _e7); 12 | let _e12: u32 = atomicSub((&atomX_naga_oil_mod_XORSXG5C7NVXWI5LMMUX), 1u); 13 | let _e13: u32 = y; 14 | y = (_e13 + _e12); 15 | let _e17: u32 = atomicMax((&atomX_naga_oil_mod_XORSXG5C7NVXWI5LMMUX), 5u); 16 | let _e18: u32 = y; 17 | y = (_e18 + _e17); 18 | let _e22: u32 = atomicMin((&atomX_naga_oil_mod_XORSXG5C7NVXWI5LMMUX), 4u); 19 | let _e23: u32 = y; 20 | y = (_e23 + _e22); 21 | let _e25: u32 = y; 22 | let _e27: u32 = atomicExchange((&atomX_naga_oil_mod_XORSXG5C7NVXWI5LMMUX), _e25); 23 | let _e28: u32 = y; 24 | y = (_e28 + _e27); 25 | let _e33: _atomic_compare_exchange_resultUint4_ = atomicCompareExchangeWeak((&atomX_naga_oil_mod_XORSXG5C7NVXWI5LMMUX), 12u, 0u); 26 | if _e33.exchanged { 27 | let _e36: u32 = y; 28 | y = (_e36 + _e33.old_value); 29 | } 30 | let _e38: u32 = y; 31 | return f32(_e38); 32 | } 33 | 34 | fn main() -> f32 { 35 | let _e0: f32 = entry_pointX_naga_oil_mod_XORSXG5C7NVXWI5LMMUX(); 36 | return _e0; 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/compose/tests/expected/bad_identifiers.txt: -------------------------------------------------------------------------------- 1 | struct IsFineX_naga_oil_mod_XON2HE5LDORZQX { 2 | fine: f32, 3 | } 4 | 5 | struct Isbad_X_naga_oil_mod_XON2HE5LDORZQX { 6 | fine_member: f32, 7 | } 8 | 9 | const fineX_naga_oil_mod_XMNXW443UOMX: f32 = 1f; 10 | const bad_X_naga_oil_mod_XMNXW443UOMX: f32 = 1f; 11 | 12 | var fineX_naga_oil_mod_XM5WG6YTBNRZQX: f32 = 1f; 13 | var bad_X_naga_oil_mod_XM5WG6YTBNRZQX: f32 = 1f; 14 | 15 | fn fineX_naga_oil_mod_XMZXHGX(in: f32) -> f32 { 16 | return in; 17 | } 18 | 19 | fn bad_X_naga_oil_mod_XMZXHGX(in_1: f32) -> f32 { 20 | return in_1; 21 | } 22 | 23 | fn main() -> f32 { 24 | var d: IsFineX_naga_oil_mod_XON2HE5LDORZQX; 25 | var e: Isbad_X_naga_oil_mod_XON2HE5LDORZQX; 26 | 27 | let _e1: f32 = fineX_naga_oil_mod_XMZXHGX(1f); 28 | let _e3: f32 = bad_X_naga_oil_mod_XMZXHGX(2f); 29 | let b: f32 = (_e1 + _e3); 30 | let _e6: f32 = fineX_naga_oil_mod_XM5WG6YTBNRZQX; 31 | let _e8: f32 = bad_X_naga_oil_mod_XM5WG6YTBNRZQX; 32 | let c: f32 = (_e6 + _e8); 33 | d.fine = 3f; 34 | e.fine_member = 4f; 35 | let _e20: f32 = d.fine; 36 | let _e23: f32 = e.fine_member; 37 | return ((((2f + b) + c) + _e20) + _e23); 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/compose/tests/expected/big_shaderdefs.txt: -------------------------------------------------------------------------------- 1 | fn fX_naga_oil_mod_XNVXWIX() -> f32 { 2 | var x: f32 = 0f; 3 | 4 | x = 1f; 5 | let _e3: f32 = x; 6 | return _e3; 7 | } 8 | 9 | fn main() -> f32 { 10 | let _e0: f32 = fX_naga_oil_mod_XNVXWIX(); 11 | return _e0; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/compose/tests/expected/conditional_import_a.txt: -------------------------------------------------------------------------------- 1 | const CX_naga_oil_mod_XMEX: u32 = 1u; 2 | 3 | fn main() -> u32 { 4 | return CX_naga_oil_mod_XMEX; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /src/compose/tests/expected/conditional_import_b.txt: -------------------------------------------------------------------------------- 1 | const CX_naga_oil_mod_XMIX: u32 = 2u; 2 | 3 | fn main() -> u32 { 4 | return CX_naga_oil_mod_XMIX; 5 | } 6 | 7 | -------------------------------------------------------------------------------- /src/compose/tests/expected/conditional_missing_import.txt: -------------------------------------------------------------------------------- 1 | error: required import 'b' not found 2 | ┌─ tests/conditional_import_fail/top.wgsl:6:12 3 | │ 4 | 6 │ return b::C; 5 | │ ^ 6 | │ 7 | = missing import 'b' 8 | 9 | -------------------------------------------------------------------------------- /src/compose/tests/expected/conditional_missing_import_nested.txt: -------------------------------------------------------------------------------- 1 | error: required import 'b' not found 2 | ┌─ tests/conditional_import_fail/top_nested.wgsl:1:1 3 | │ 4 | 1 │ #import middle 5 | │ ^ 6 | │ 7 | = missing import 'b' 8 | 9 | -------------------------------------------------------------------------------- /src/compose/tests/expected/diagnostic_filters.txt: -------------------------------------------------------------------------------- 1 | @group(0) @binding(0) var s : sampler; 2 | @group(0) @binding(2) var tex : texture_2d; 3 | @group(1) @binding(0) var ro_buffer : array; 4 | 5 | @fragment 6 | fn main(@builtin(position) p : vec4f) -> @location(0) vec4f { 7 | return filters::diagnostic_test(); 8 | } 9 | 10 | diagnostic(warning, derivative_uniformity); 11 | 12 | fn diagnostic_test() -> vec4f { 13 | diagnostic(off, derivative_uniformity); 14 | if ro_buffer[0] == 0 { 15 | // Emits a derivative uniformity error during validation. 16 | return textureSample(tex, s, vec2(0.,0.)); 17 | } 18 | 19 | return vec4f(0.); 20 | } 21 | -------------------------------------------------------------------------------- /src/compose/tests/expected/dup_import.txt: -------------------------------------------------------------------------------- 1 | const PIX_naga_oil_mod_XMNXW443UOMX: f32 = 3.1f; 2 | 3 | fn fX_naga_oil_mod_XMEX() -> f32 { 4 | return 3.1f; 5 | } 6 | 7 | fn fX_naga_oil_mod_XMIX() -> f32 { 8 | return 6.2f; 9 | } 10 | 11 | fn main() -> f32 { 12 | let _e0: f32 = fX_naga_oil_mod_XMEX(); 13 | let _e1: f32 = fX_naga_oil_mod_XMIX(); 14 | return (_e0 * _e1); 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/compose/tests/expected/dup_struct_import.txt: -------------------------------------------------------------------------------- 1 | struct MyStructX_naga_oil_mod_XON2HE5LDOQX { 2 | value: f32, 3 | } 4 | 5 | fn aX_naga_oil_mod_XMEX() -> MyStructX_naga_oil_mod_XON2HE5LDOQX { 6 | var s_a: MyStructX_naga_oil_mod_XON2HE5LDOQX; 7 | 8 | s_a.value = 1f; 9 | let _e3: MyStructX_naga_oil_mod_XON2HE5LDOQX = s_a; 10 | return _e3; 11 | } 12 | 13 | fn bX_naga_oil_mod_XMIX() -> MyStructX_naga_oil_mod_XON2HE5LDOQX { 14 | var s_b: MyStructX_naga_oil_mod_XON2HE5LDOQX; 15 | 16 | s_b.value = 2f; 17 | let _e3: MyStructX_naga_oil_mod_XON2HE5LDOQX = s_b; 18 | return _e3; 19 | } 20 | 21 | fn main() -> f32 { 22 | let _e0: MyStructX_naga_oil_mod_XON2HE5LDOQX = aX_naga_oil_mod_XMEX(); 23 | let _e1: MyStructX_naga_oil_mod_XON2HE5LDOQX = bX_naga_oil_mod_XMIX(); 24 | return (_e0.value / _e1.value); 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/compose/tests/expected/err_parse.txt: -------------------------------------------------------------------------------- 1 | error: no definition in scope for identifier: `zdd` 2 | ┌─ tests/error_test/wgsl_parse_err.wgsl:16:12 3 | │ 4 | 16 │ return zdd; 5 | │ ^^^ unknown identifier 6 | │ 7 | = no definition in scope for identifier: `zdd` 8 | 9 | -------------------------------------------------------------------------------- /src/compose/tests/expected/err_validation_1.txt: -------------------------------------------------------------------------------- 1 | error: failed to build a valid final module: Function [1] 'func' is invalid 2 | ┌─ tests/error_test/wgsl_valid_err.wgsl:7:1 3 | │ 4 | 7 │ ╭ fn func() -> f32 { 5 | 8 │ │ return 1u; 6 | │ │ ^^ naga::ir::Expression [0] 7 | │ ╰──────────────^ naga::ir::Function [1] 8 | │ 9 | = The `return` value Some([0]) does not match the function return value 10 | 11 | -------------------------------------------------------------------------------- /src/compose/tests/expected/err_validation_2.txt: -------------------------------------------------------------------------------- 1 | error: failed to build a valid final module: Function [0] 'valid_inc::func' is invalid 2 | ┌─ tests/error_test/wgsl_valid_err.wgsl:7:1 3 | │ 4 | 7 │ ╭ fn func() -> f32 { 5 | 8 │ │ return 1u; 6 | │ │ ^^ naga::ir::Expression [0] 7 | │ ╰──────────────^ naga::ir::Function [0] 8 | │ 9 | = The `return` value Some([0]) does not match the function return value 10 | 11 | -------------------------------------------------------------------------------- /src/compose/tests/expected/glsl_call_wgsl.txt: -------------------------------------------------------------------------------- 1 | struct VertexOutput { 2 | @builtin(position) gl_Position: vec4, 3 | } 4 | 5 | var gl_Position: vec4; 6 | 7 | fn wgsl_funcX_naga_oil_mod_XO5TXG3C7NVXWI5LMMUX() -> f32 { 8 | return 53f; 9 | } 10 | 11 | fn main_1() { 12 | let _e0: f32 = wgsl_funcX_naga_oil_mod_XO5TXG3C7NVXWI5LMMUX(); 13 | gl_Position = vec4(_e0); 14 | return; 15 | } 16 | 17 | @vertex 18 | fn main() -> VertexOutput { 19 | main_1(); 20 | let _e1: vec4 = gl_Position; 21 | return VertexOutput(_e1); 22 | } 23 | -------------------------------------------------------------------------------- /src/compose/tests/expected/glsl_const_import.txt: -------------------------------------------------------------------------------- 1 | struct FragmentOutput { 2 | @location(0) out_color: vec4, 3 | } 4 | 5 | const my_constantX_naga_oil_mod_XMNXW23LPNYX: f32 = 0.5f; 6 | 7 | var out_color: vec4; 8 | 9 | fn main_1() { 10 | out_color = vec4(1f, 0.5f, 0f, 1f); 11 | return; 12 | } 13 | 14 | @fragment 15 | fn main() -> FragmentOutput { 16 | main_1(); 17 | let _e1: vec4 = out_color; 18 | return FragmentOutput(_e1); 19 | } 20 | -------------------------------------------------------------------------------- /src/compose/tests/expected/glsl_wgsl_const_import.txt: -------------------------------------------------------------------------------- 1 | const my_constantX_naga_oil_mod_XMNXW23LPNYX: f32 = 0.5f; 2 | 3 | fn main() -> vec4 { 4 | return vec4(1f, 0.5f, 0f, 1f); 5 | } 6 | 7 | -------------------------------------------------------------------------------- /src/compose/tests/expected/import_in_decl.txt: -------------------------------------------------------------------------------- 1 | const XX_naga_oil_mod_XMNXW443UOMX: u32 = 1u; 2 | 3 | var arrX_naga_oil_mod_XMJUW4ZAX: array; 4 | 5 | fn main() -> f32 { 6 | let _e2: u32 = arrX_naga_oil_mod_XMJUW4ZAX[0]; 7 | return f32(_e2); 8 | } 9 | 10 | -------------------------------------------------------------------------------- /src/compose/tests/expected/invalid_override_base.txt: -------------------------------------------------------------------------------- 1 | error: override is invalid as `outer` is not virtual (this error can be disabled with feature 'override_any') 2 | ┌─ tests/overrides/top_invalid.wgsl:3:13 3 | │ 4 | 3 │ override fn mod::outer() -> f32 { 5 | │ ^ 6 | 7 | -------------------------------------------------------------------------------- /src/compose/tests/expected/item_import_test.txt: -------------------------------------------------------------------------------- 1 | const XX_naga_oil_mod_XMNXW443UOMX: u32 = 1u; 2 | const YX_naga_oil_mod_XMNXW443UOMX: u32 = 2u; 3 | 4 | fn doubleX_naga_oil_mod_XMNXW443UOMX(in: u32) -> u32 { 5 | return (in * 2u); 6 | } 7 | 8 | fn main() -> u32 { 9 | let _e1: u32 = doubleX_naga_oil_mod_XMNXW443UOMX(XX_naga_oil_mod_XMNXW443UOMX); 10 | return _e1; 11 | } 12 | 13 | fn other() -> u32 { 14 | let _e1: u32 = doubleX_naga_oil_mod_XMNXW443UOMX(YX_naga_oil_mod_XMNXW443UOMX); 15 | return _e1; 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/compose/tests/expected/item_sub_point.txt: -------------------------------------------------------------------------------- 1 | struct FragX_naga_oil_mod_XNVXWIX { 2 | fragment: f32, 3 | } 4 | 5 | fn fragmentX_naga_oil_mod_XNVXWIX(f_1: FragX_naga_oil_mod_XNVXWIX) -> f32 { 6 | return (f_1.fragment * 2f); 7 | } 8 | 9 | @fragment 10 | fn main() -> @location(0) f32 { 11 | var f: FragX_naga_oil_mod_XNVXWIX; 12 | 13 | f.fragment = 3f; 14 | let _e3: FragX_naga_oil_mod_XNVXWIX = f; 15 | let _e4: f32 = fragmentX_naga_oil_mod_XNVXWIX(_e3); 16 | return _e4; 17 | } 18 | -------------------------------------------------------------------------------- /src/compose/tests/expected/missing_import.txt: -------------------------------------------------------------------------------- 1 | error: required import 'missing' not found 2 | ┌─ tests/error_test/include.wgsl:11:13 3 | │ 4 | 11 │ let x = missing::y(); 5 | │ ^ 6 | │ 7 | = missing import 'missing' 8 | 9 | -------------------------------------------------------------------------------- /src/compose/tests/expected/simple_compose.txt: -------------------------------------------------------------------------------- 1 | fn helloX_naga_oil_mod_XNFXGGX() -> f32 { 2 | return 1f; 3 | } 4 | 5 | fn main() -> f32 { 6 | let _e0: f32 = helloX_naga_oil_mod_XNFXGGX(); 7 | return _e0; 8 | } 9 | 10 | -------------------------------------------------------------------------------- /src/compose/tests/expected/test_quoted_import_dup_name.txt: -------------------------------------------------------------------------------- 1 | fn fooX_naga_oil_mod_XEJYXK33UMVSF63LPMR2WYZJCX() -> f32 { 2 | return 3f; 3 | } 4 | 5 | fn myfunc(foo: u32) -> f32 { 6 | return (f32(foo) * 2f); 7 | } 8 | 9 | fn main() -> f32 { 10 | let _e1: f32 = myfunc(1u); 11 | let _e2: f32 = fooX_naga_oil_mod_XEJYXK33UMVSF63LPMR2WYZJCX(); 12 | return (_e1 + _e2); 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/compose/tests/expected/use_shared_global.txt: -------------------------------------------------------------------------------- 1 | var aX_naga_oil_mod_XNVXWIX: f32 = 0f; 2 | 3 | fn add() { 4 | let _e2: f32 = aX_naga_oil_mod_XNVXWIX; 5 | aX_naga_oil_mod_XNVXWIX = (_e2 + 1f); 6 | return; 7 | } 8 | 9 | fn main() -> f32 { 10 | add(); 11 | add(); 12 | let _e1: f32 = aX_naga_oil_mod_XNVXWIX; 13 | return _e1; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/compose/tests/expected/wgsl_call_entrypoint.txt: -------------------------------------------------------------------------------- 1 | fn fragmentX_naga_oil_mod_XNFXGG3DVMRSQX(frag_coord_1: vec4) -> vec4 { 2 | return vec4((1.5f * frag_coord_1)); 3 | } 4 | 5 | @fragment 6 | fn fragment(@builtin(position) frag_coord: vec4) -> @location(0) vec4 { 7 | let _e1: vec4 = fragmentX_naga_oil_mod_XNFXGG3DVMRSQX(frag_coord); 8 | return _e1; 9 | } 10 | -------------------------------------------------------------------------------- /src/compose/tests/expected/wgsl_call_glsl.txt: -------------------------------------------------------------------------------- 1 | struct CustomMaterialX_naga_oil_mod_XM5WHG3C7NVXWI5LMMUX { 2 | Color: vec4, 3 | } 4 | 5 | @group(1) @binding(0) 6 | var global: CustomMaterialX_naga_oil_mod_XM5WHG3C7NVXWI5LMMUX; 7 | 8 | fn glsl_funcX_naga_oil_mod_XM5WHG3C7NVXWI5LMMUX() -> f32 { 9 | return 3f; 10 | } 11 | 12 | fn fraggo() -> f32 { 13 | let _e0: f32 = glsl_funcX_naga_oil_mod_XM5WHG3C7NVXWI5LMMUX(); 14 | return _e0; 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/compose/tests/expected/wgsl_glsl_const_import.txt: -------------------------------------------------------------------------------- 1 | struct FragmentOutput { 2 | @location(0) out_color: vec4, 3 | } 4 | 5 | const my_constantX_naga_oil_mod_XMNXW23LPNYX: f32 = 0.5f; 6 | 7 | var out_color: vec4; 8 | 9 | fn main_1() { 10 | out_color = vec4(1f, 0.5f, 0f, 1f); 11 | return; 12 | } 13 | 14 | @fragment 15 | fn main() -> FragmentOutput { 16 | main_1(); 17 | let _e1: vec4 = out_color; 18 | return FragmentOutput(_e1); 19 | } 20 | -------------------------------------------------------------------------------- /src/compose/tests/glsl/basic.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | float _xaga_oil_mod__wgsl_module__wgsl_func() { 3 | return 53.0; 4 | } 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | layout(location = 0) out vec4 o_Target; 15 | 16 | void main() { 17 | o_Target = vec4(_xaga_oil_mod__wgsl_module__wgsl_func()); 18 | } 19 | -------------------------------------------------------------------------------- /src/compose/tests/glsl/module.glsl: -------------------------------------------------------------------------------- 1 | #define_import_path glsl_module 2 | 3 | #version 450 4 | layout(location = 0) in vec2 v_Uv; 5 | 6 | layout(location = 0) out vec4 o_Target; 7 | 8 | layout(set = 1, binding = 0) uniform CustomMaterial { 9 | vec4 Color; 10 | }; 11 | 12 | layout(set = 1, binding = 1) uniform texture2D CustomMaterial_texture; 13 | layout(set = 1, binding = 2) uniform sampler CustomMaterial_sampler; 14 | 15 | 16 | void main() { 17 | o_Target = Color * texture(sampler2D(CustomMaterial_texture,CustomMaterial_sampler), v_Uv); 18 | } 19 | 20 | float glsl_func() { 21 | return 3.0; 22 | } -------------------------------------------------------------------------------- /src/compose/tests/glsl/module.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path wgsl_module 2 | 3 | fn wgsl_func() -> f32 { 4 | return 53.0; 5 | } -------------------------------------------------------------------------------- /src/compose/tests/glsl/top.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #import wgsl_module 4 | 5 | 6 | 7 | void main() { 8 | gl_Position = vec4(wgsl_module::wgsl_func()); 9 | } -------------------------------------------------------------------------------- /src/compose/tests/glsl/top.wgsl: -------------------------------------------------------------------------------- 1 | #import glsl_module 2 | 3 | fn fraggo() -> f32 { 4 | let x = glsl_module::glsl_func(); 5 | return x; 6 | } -------------------------------------------------------------------------------- /src/compose/tests/glsl_const_import/consts.glsl: -------------------------------------------------------------------------------- 1 | #define_import_path common 2 | 3 | const float my_constant = 0.5; 4 | 5 | void main() {} -------------------------------------------------------------------------------- /src/compose/tests/glsl_const_import/consts.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path common 2 | 3 | const my_constant: f32 = 0.5; 4 | -------------------------------------------------------------------------------- /src/compose/tests/glsl_const_import/top.glsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #import common 4 | 5 | out vec4 out_color; 6 | 7 | void main() { 8 | out_color = vec4(1, common::my_constant, 0, 1); 9 | } -------------------------------------------------------------------------------- /src/compose/tests/glsl_const_import/top.wgsl: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #import common 4 | 5 | fn main() -> vec4 { 6 | return vec4(1.0, common::my_constant, 0.0, 1.0); 7 | } -------------------------------------------------------------------------------- /src/compose/tests/invalid_identifiers/const.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path consts 2 | 3 | const fine: f32 = 1.0; 4 | const bad_: f32 = 1.0; -------------------------------------------------------------------------------- /src/compose/tests/invalid_identifiers/fn.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path fns 2 | 3 | fn fine(in: f32) -> f32 {return in;} 4 | fn bad_(in: f32) -> f32 {return in;} 5 | -------------------------------------------------------------------------------- /src/compose/tests/invalid_identifiers/global.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path globals 2 | 3 | var fine: f32 = 1.0; 4 | var bad_: f32 = 1.0; -------------------------------------------------------------------------------- /src/compose/tests/invalid_identifiers/struct.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path structs 2 | 3 | struct IsFine { 4 | fine: f32, 5 | } 6 | 7 | struct Isbad_ { 8 | fine_member: f32, 9 | } -------------------------------------------------------------------------------- /src/compose/tests/invalid_identifiers/struct_member.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path struct_members 2 | 3 | struct FineStruct { 4 | fine: f32, 5 | } 6 | 7 | struct BadStruct { 8 | also_fine: f32, 9 | bad_: f32, 10 | } -------------------------------------------------------------------------------- /src/compose/tests/invalid_identifiers/top_invalid.wgsl: -------------------------------------------------------------------------------- 1 | // #import consts 2 | // #import fns 3 | // #import globals 4 | #import struct_members 5 | // #import structs 6 | 7 | fn main() -> f32 { 8 | // let a = consts::fine + consts::bad_; 9 | // let b = fns::fine(1.0) + fns::bad_(2.0); 10 | // let c = globals::fine + globals::bad_; 11 | // var d: structs::IsFine; 12 | // d.fine = 3.0; 13 | // var e: structs::Isbad_; 14 | // e.fine_member = 4.0; 15 | var f = struct_members::FineStruct; 16 | f.fine = 5.0; 17 | var g = struct_members::BadStruct; 18 | g.also_fine = 6.0; 19 | g.bad_ = 7.0; 20 | 21 | // return a + b + c + d.fine + e.fine_member; 22 | return f.fine + g.also_fine + g.bad_; 23 | } -------------------------------------------------------------------------------- /src/compose/tests/invalid_identifiers/top_valid.wgsl: -------------------------------------------------------------------------------- 1 | #import consts 2 | #import fns 3 | #import globals 4 | // #import struct_members 5 | #import structs 6 | 7 | fn main() -> f32 { 8 | let a = consts::fine + consts::bad_; 9 | let b = fns::fine(1.0) + fns::bad_(2.0); 10 | let c = globals::fine + globals::bad_; 11 | var d: structs::IsFine; 12 | d.fine = 3.0; 13 | var e: structs::Isbad_; 14 | e.fine_member = 4.0; 15 | // var f = struct_members::FineStruct; 16 | // f.fine = 5.0; 17 | // var g = struct_members::BadStruct; 18 | // g.also_fine = 6.0; 19 | // g.bad_ = 7.0; 20 | 21 | return a + b + c + d.fine + e.fine_member; // + f.fine + g.also_fine + g.bad_; 22 | } -------------------------------------------------------------------------------- /src/compose/tests/item_import/consts.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path consts 2 | 3 | const X: u32 = 1u; 4 | const Y: u32 = 2u; 5 | const Z: u32 = 3u; 6 | 7 | @group(0) @binding(0) 8 | var something: sampler; 9 | 10 | fn double(in: u32) -> u32 { 11 | return in * 2u; 12 | } 13 | 14 | fn triple(in: u32) -> u32 { 15 | return in * 3u; 16 | } -------------------------------------------------------------------------------- /src/compose/tests/item_import/top.wgsl: -------------------------------------------------------------------------------- 1 | #import consts X, double 2 | #import consts Y 3 | 4 | fn main() -> u32 { 5 | return double(X); 6 | } 7 | 8 | fn other() -> u32 { 9 | return double(Y); 10 | } -------------------------------------------------------------------------------- /src/compose/tests/item_sub_point/mod.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path mod 2 | 3 | struct Frag { 4 | fragment: f32, 5 | } 6 | 7 | fn fragment(f: Frag) -> f32 { 8 | return f.fragment * 2.0; 9 | } -------------------------------------------------------------------------------- /src/compose/tests/item_sub_point/top.wgsl: -------------------------------------------------------------------------------- 1 | #import mod Frag, fragment 2 | 3 | @fragment 4 | fn main() -> @location(0) f32 { 5 | var f: Frag; 6 | f.fragment = 3.0; 7 | return fragment(f); 8 | } -------------------------------------------------------------------------------- /src/compose/tests/modf/mod.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path test_module 2 | 3 | fn fract_times_whole(x: f32) -> f32 { 4 | let fw = modf(x); 5 | let f = fw.fract; 6 | let w = fw.whole; 7 | return f * w; 8 | } 9 | 10 | fn entry_point() -> f32 { 11 | let fract_times_whole = fract_times_whole(3.25); 12 | return fract_times_whole - 0.75; 13 | } 14 | -------------------------------------------------------------------------------- /src/compose/tests/modf/top.wgsl: -------------------------------------------------------------------------------- 1 | #import test_module 2 | 3 | fn entry_point() -> f32 { 4 | let fract_times_whole = test_module::fract_times_whole(3.1); 5 | return fract_times_whole - 0.3; 6 | } 7 | -------------------------------------------------------------------------------- /src/compose/tests/overrides/middle.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path middle 2 | 3 | #import mod 4 | 5 | override fn mod::inner(arg: f32) -> f32 { 6 | return arg * 3.0; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /src/compose/tests/overrides/mod.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path mod 2 | 3 | virtual fn inner(arg: f32) -> f32 { 4 | return arg * 2.0; 5 | } 6 | 7 | fn outer() -> f32 { 8 | return inner(1.0); 9 | } -------------------------------------------------------------------------------- /src/compose/tests/overrides/top.wgsl: -------------------------------------------------------------------------------- 1 | #import mod 2 | 3 | override fn mod::inner(arg: f32) -> f32 { 4 | return arg * 3.0; 5 | } 6 | 7 | fn top() -> f32 { 8 | return mod::outer(); 9 | } -------------------------------------------------------------------------------- /src/compose/tests/overrides/top_invalid.wgsl: -------------------------------------------------------------------------------- 1 | #import mod 2 | 3 | override fn mod::outer() -> f32 { 4 | return 99.0; 5 | } 6 | 7 | fn top() -> f32 { 8 | return mod::outer(); 9 | } -------------------------------------------------------------------------------- /src/compose/tests/overrides/top_with_middle.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path test_module 2 | 3 | #import middle 4 | #import mod 5 | 6 | fn entry_point() -> f32 { 7 | return mod::outer(); 8 | } -------------------------------------------------------------------------------- /src/compose/tests/quoted_dup/mod.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path "quoted_module" 2 | 3 | fn foo() -> f32 { 4 | return 3.0; 5 | } -------------------------------------------------------------------------------- /src/compose/tests/quoted_dup/top.wgsl: -------------------------------------------------------------------------------- 1 | #import "quoted_module" as foo; 2 | 3 | fn myfunc(foo: u32) -> f32 { 4 | return f32(foo) * 2.0; 5 | } 6 | 7 | fn main() -> f32 { 8 | return myfunc(1u) + foo::foo(); 9 | } -------------------------------------------------------------------------------- /src/compose/tests/raycast/mod.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path test_module 2 | 3 | @group(0) @binding(0) var tlas: acceleration_structure; 4 | 5 | const RAY_NO_CULL = 0xFFu; 6 | 7 | fn ray_func() -> RayIntersection { 8 | let ray = RayDesc(0u, RAY_NO_CULL, 0.0001, 100000.0, vec3(0.0, 0.0, 0.0), vec3(1.0, 0.0, 0.0)); 9 | var rq: ray_query; 10 | rayQueryInitialize(&rq, tlas, ray); 11 | rayQueryProceed(&rq); 12 | return rayQueryGetCommittedIntersection(&rq); 13 | } 14 | -------------------------------------------------------------------------------- /src/compose/tests/raycast/top.wgsl: -------------------------------------------------------------------------------- 1 | #import test_module 2 | 3 | fn main() -> f32 { 4 | let ray = test_module::ray_func(); 5 | return ray.t; 6 | } 7 | -------------------------------------------------------------------------------- /src/compose/tests/rusty_imports/mod_a_b_c.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path a::b::c 2 | 3 | const C: f32 = 2.0; 4 | 5 | fn triple(in: f32) -> f32 { 6 | return in * 3.0; 7 | } -------------------------------------------------------------------------------- /src/compose/tests/rusty_imports/mod_a_x.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path a::x 2 | 3 | fn square(in: f32) -> f32 { 4 | return in * in; 5 | } -------------------------------------------------------------------------------- /src/compose/tests/rusty_imports/top.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path test_module 2 | 3 | #import a::b as partial_path 4 | #import a::b::c as full_path 5 | 6 | fn entry_point() -> f32 { 7 | return a::x::square(partial_path::c::triple(full_path::C)); 8 | } -------------------------------------------------------------------------------- /src/compose/tests/simple/inc.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path inc 2 | 3 | fn hello() -> f32 { 4 | return 1.0; 5 | } -------------------------------------------------------------------------------- /src/compose/tests/simple/top.wgsl: -------------------------------------------------------------------------------- 1 | #import inc as Inc 2 | 3 | fn main() -> f32 { 4 | let x = Inc::hello(); 5 | return x; 6 | } -------------------------------------------------------------------------------- /src/compose/tests/use_shared_global/mod.wgsl: -------------------------------------------------------------------------------- 1 | #define_import_path mod 2 | 3 | var a: f32 = 0.0; -------------------------------------------------------------------------------- /src/compose/tests/use_shared_global/top.wgsl: -------------------------------------------------------------------------------- 1 | #import mod 2 | 3 | fn add() { 4 | mod::a += 1.0; 5 | } 6 | 7 | fn main() -> f32 { 8 | add(); 9 | add(); 10 | return mod::a; 11 | } -------------------------------------------------------------------------------- /src/compose/tokenizer.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | 3 | #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] 4 | pub enum Token<'a> { 5 | Identifier(&'a str, usize), 6 | Other(char, usize), 7 | Whitespace(&'a str, usize), 8 | } 9 | 10 | impl Token<'_> { 11 | pub fn pos(&self) -> usize { 12 | match self { 13 | Token::Identifier(_, pos) | Token::Other(_, pos) | Token::Whitespace(_, pos) => *pos, 14 | } 15 | } 16 | 17 | pub fn identifier(&self) -> Option<&str> { 18 | match self { 19 | Token::Identifier(ident, _) => Some(ident), 20 | _ => None, 21 | } 22 | } 23 | } 24 | 25 | #[derive(Clone, Copy, PartialEq, Eq)] 26 | enum TokenKind { 27 | Identifier, 28 | Whitespace, 29 | } 30 | 31 | // a basic tokenizer that separates identifiers from non-identifiers, and optionally returns whitespace tokens 32 | // unicode XID rules apply, except that additional characters '"' and '::' (sequences of two colons) are allowed in identifiers. 33 | // quotes treat any further chars until the next quote as part of the identifier. 34 | // note we don't support non-USV identifiers like 👩‍👩‍👧‍👧 which is apparently in XID_continue 35 | pub struct Tokenizer<'a> { 36 | tokens: VecDeque>, 37 | } 38 | 39 | impl<'a> Tokenizer<'a> { 40 | pub fn new(src: &'a str, emit_whitespace: bool) -> Self { 41 | let mut tokens = VecDeque::default(); 42 | let mut current_token_start = 0; 43 | let mut current_token = None; 44 | let mut quoted_token = false; 45 | 46 | let mut chars = src.char_indices().peekable(); 47 | 48 | while let Some((ix, char)) = chars.next() { 49 | if char == '"' { 50 | quoted_token = !quoted_token; 51 | if !quoted_token { 52 | continue; 53 | } 54 | } 55 | 56 | if let Some(tok) = current_token { 57 | match tok { 58 | TokenKind::Identifier => { 59 | // accept anything within quotes, or XID_continues 60 | if quoted_token || unicode_ident::is_xid_continue(char) { 61 | continue; 62 | } 63 | // accept `::` 64 | if char == ':' && chars.peek() == Some(&(ix + 1, ':')) { 65 | chars.next(); 66 | continue; 67 | } 68 | 69 | tokens.push_back(Token::Identifier( 70 | &src[current_token_start..ix], 71 | current_token_start, 72 | )); 73 | } 74 | TokenKind::Whitespace => { 75 | if char.is_whitespace() { 76 | continue; 77 | } 78 | tokens.push_back(Token::Whitespace( 79 | &src[current_token_start..ix], 80 | current_token_start, 81 | )); 82 | } 83 | }; 84 | 85 | current_token_start = ix; 86 | current_token = None; 87 | } 88 | 89 | if quoted_token || unicode_ident::is_xid_start(char) { 90 | current_token = Some(TokenKind::Identifier); 91 | current_token_start = ix; 92 | } else if !char.is_whitespace() { 93 | tokens.push_back(Token::Other(char, ix)); 94 | } else if char.is_whitespace() && emit_whitespace { 95 | current_token = Some(TokenKind::Whitespace); 96 | current_token_start = ix; 97 | } 98 | } 99 | 100 | if let Some(tok) = current_token { 101 | match tok { 102 | TokenKind::Identifier => { 103 | tokens.push_back(Token::Identifier( 104 | &src[current_token_start..src.len()], 105 | current_token_start, 106 | )); 107 | } 108 | TokenKind::Whitespace => { 109 | tokens.push_back(Token::Whitespace( 110 | &src[current_token_start..src.len()], 111 | current_token_start, 112 | )); 113 | } 114 | }; 115 | } 116 | 117 | Self { tokens } 118 | } 119 | } 120 | 121 | impl<'a> Iterator for Tokenizer<'a> { 122 | type Item = Token<'a>; 123 | 124 | fn next(&mut self) -> Option { 125 | self.tokens.pop_front() 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // todo 2 | 3 | // prune 4 | // check if you can ImageStore to a function argument - pretty sure not 5 | // barriers - we currently don't make the containing scope required on encountering a barrier. this doesn't feel right since a nested barrier could be ignored? 6 | // * atomics 7 | 8 | // compose 9 | // use more regexes..? 10 | // generate headers on demand 11 | // check mobile - does everybody use wgsl? 12 | // support glsl compute 13 | // move tests to executed compute test 14 | // * purge/replace modules should invalidate dependents 15 | // * search/replace decorated strings in error reports 16 | // * use better encoding for decorate 17 | // * don't allow modules containing decoration 18 | 19 | // derive 20 | // * better api for entry points 21 | 22 | // redirect 23 | // stable output order where possible? 24 | // translate access uniform / storage / etc 25 | 26 | pub mod compose; 27 | pub mod derive; 28 | pub mod redirect; 29 | 30 | #[cfg(feature = "prune")] 31 | pub mod prune; 32 | -------------------------------------------------------------------------------- /src/prune/test.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | use crate::prune::{PartReq, Pruner}; 3 | 4 | use super::*; 5 | use naga::{ 6 | back::wgsl::WriterFlags, 7 | valid::{Capabilities, ValidationFlags}, 8 | }; 9 | 10 | #[test] 11 | fn it_works() { 12 | let shader_src = include_str!("tests/test.wgsl"); 13 | let shader = naga::front::wgsl::parse_str(shader_src).unwrap(); 14 | println!("{:#?}", shader); 15 | 16 | let info = naga::valid::Validator::new(ValidationFlags::all(), Capabilities::default()) 17 | .validate(&shader) 18 | .unwrap(); 19 | let text = naga::back::wgsl::write_string(&shader, &info, WriterFlags::EXPLICIT_TYPES).unwrap(); 20 | println!("\n\nbase wgsl:\n{}", text); 21 | 22 | let mut modreq = Pruner::new(&shader); 23 | let func = shader 24 | .functions 25 | .fetch_if(|f| f.name == Some("test".to_string())) 26 | .unwrap(); 27 | let input_req = modreq.add_function( 28 | func, 29 | Default::default(), 30 | Some(PartReq::Part([(0, PartReq::All)].into())), 31 | ); 32 | 33 | println!("\n\ninput_req:\n{:#?}", input_req); 34 | println!("\n\nmodreq:\n{:#?}", modreq); 35 | 36 | let rewritten_shader = modreq.rewrite(); 37 | 38 | println!("\n\nrewritten_shader:\n{:#?}", rewritten_shader); 39 | 40 | let info = naga::valid::Validator::new(ValidationFlags::all(), Capabilities::default()) 41 | .validate(&rewritten_shader) 42 | .unwrap(); 43 | let text = 44 | naga::back::wgsl::write_string(&rewritten_shader, &info, WriterFlags::EXPLICIT_TYPES) 45 | .unwrap(); 46 | println!("\n\nwgsl:\n{}", text); 47 | } 48 | 49 | #[test] 50 | fn frag_reduced() { 51 | let shader_src = include_str!("tests/frag_reduced.wgsl"); 52 | let shader = naga::front::wgsl::parse_str(shader_src).unwrap(); 53 | 54 | let mut pruner = Pruner::new(&shader); 55 | let context = pruner.add_entrypoint( 56 | shader.entry_points.get(0).unwrap(), 57 | Default::default(), 58 | None, 59 | ); 60 | println!("{:?}", context); 61 | } 62 | 63 | #[test] 64 | fn frag_reduced_2() { 65 | let shader_src = include_str!("tests/frag_reduced_2.wgsl"); 66 | let shader = naga::front::wgsl::parse_str(shader_src).unwrap(); 67 | 68 | let mut pruner = Pruner::new(&shader); 69 | let context = pruner.add_entrypoint( 70 | shader.entry_points.get(0).unwrap(), 71 | Default::default(), 72 | None, 73 | ); 74 | println!("{:?}", context); 75 | } 76 | 77 | #[test] 78 | fn pbr_reduced() { 79 | let subscriber = tracing_subscriber::fmt() 80 | .with_max_level(tracing::Level::DEBUG) 81 | .finish(); 82 | let _trace = tracing::subscriber::set_default(subscriber); 83 | 84 | let shader_src = include_str!("tests/pbr_reduced.wgsl"); 85 | let shader = naga::front::wgsl::parse_str(shader_src).unwrap(); 86 | 87 | println!("{:#?}", shader); 88 | 89 | let mut pruner = Pruner::new(&shader); 90 | let context = pruner.add_entrypoint( 91 | shader.entry_points.get(0).unwrap(), 92 | Default::default(), 93 | None, 94 | ); 95 | println!("{:?}", context); 96 | context.globals_for_module(&shader); 97 | 98 | let rewrite = pruner.rewrite(); 99 | let info = naga::valid::Validator::new(ValidationFlags::all(), Capabilities::default()) 100 | .validate(&rewrite) 101 | .unwrap(); 102 | let text = 103 | naga::back::wgsl::write_string(&rewrite, &info, WriterFlags::EXPLICIT_TYPES).unwrap(); 104 | println!("\n\nwgsl:\n{}", text); 105 | } 106 | 107 | #[test] 108 | fn pbr_fn() { 109 | let subscriber = tracing_subscriber::fmt() 110 | .with_max_level(tracing::Level::DEBUG) 111 | .finish(); 112 | let _trace = tracing::subscriber::set_default(subscriber); 113 | 114 | let shader_src = include_str!("tests/pbr_fn.wgsl"); 115 | let shader = naga::front::wgsl::parse_str(shader_src).unwrap(); 116 | 117 | println!("{:#?}", shader); 118 | 119 | let mut pruner = Pruner::new(&shader); 120 | let (req, context) = pruner.add_function( 121 | shader.functions.iter().next().unwrap().0, 122 | Default::default(), 123 | None, 124 | ); 125 | println!("{}: {:?}", req, context); 126 | context.globals_for_module(&shader); 127 | 128 | let rewrite = pruner.rewrite(); 129 | let info = naga::valid::Validator::new(ValidationFlags::all(), Capabilities::default()) 130 | .validate(&rewrite) 131 | .unwrap(); 132 | let text = 133 | naga::back::wgsl::write_string(&rewrite, &info, WriterFlags::EXPLICIT_TYPES).unwrap(); 134 | println!("\n\nwgsl:\n{}", text); 135 | } 136 | -------------------------------------------------------------------------------- /src/prune/tests/frag_reduced.wgsl: -------------------------------------------------------------------------------- 1 | struct Input { 2 | one: bool, 3 | two: bool, 4 | three: bool, 5 | } 6 | 7 | fn inner(in: Input) { 8 | if (in.one && in.two) { 9 | discard; 10 | } 11 | } 12 | 13 | @fragment 14 | fn outer(thing: bool, thing2: bool, thing3: bool) { 15 | var in: Input; 16 | in.one = thing; 17 | in.two = thing2; 18 | in.three = thing3; 19 | inner(in); 20 | } -------------------------------------------------------------------------------- /src/prune/tests/frag_reduced_2.wgsl: -------------------------------------------------------------------------------- 1 | struct Vertex { 2 | a: bool, 3 | b: bool, 4 | c: bool, 5 | } 6 | 7 | struct Input { 8 | one: bool, 9 | two: bool, 10 | three: bool, 11 | } 12 | 13 | fn inner(in: Input) { 14 | if (in.one == in.two) { 15 | discard; 16 | } 17 | } 18 | 19 | @fragment 20 | fn outer(v: Vertex) { 21 | var input: Input; 22 | 23 | input.one = v.a; 24 | input.two = v.b; 25 | input.three = v.c; 26 | 27 | inner(input); 28 | } -------------------------------------------------------------------------------- /src/prune/tests/import.wgsl: -------------------------------------------------------------------------------- 1 | struct InStruct { 2 | @location(0) attr_in_struct: vec4, 3 | } 4 | 5 | // ok 6 | @fragment 7 | fn fragment( 8 | struct_in_param: InStruct, 9 | @location(1) attr_in_param: vec4, 10 | ) {} 11 | 12 | 13 | // struct NestedStruct { 14 | // @location(0) attr_in_nested_struct: vec4, 15 | // } 16 | // 17 | // struct InStruct { 18 | // nested_struct: NestedStruct, 19 | // @location(1) attr_in_struct: vec4, 20 | // } 21 | // 22 | // // fail 23 | // // `Err` value: WithSpan { inner: EntryPoint { stage: Fragment, name: "fragment", error: Argument(0, MemberMissingBinding(0)) }, spans: [(Span { start: 284, end: 387 }, "naga::Type [3]")] } 24 | // @fragment 25 | // fn fragment( 26 | // struct_in_param: InStruct, 27 | // ) {} -------------------------------------------------------------------------------- /src/prune/tests/pbr_fn.wgsl: -------------------------------------------------------------------------------- 1 | struct pbr_types__StandardMaterial { 2 | base_color: vec4, 3 | emissive: vec4, 4 | perceptual_roughness: f32, 5 | metallic: f32, 6 | reflectance: f32, 7 | flags: u32, 8 | alpha_cutoff: f32, 9 | } 10 | 11 | struct pbr_functions__PbrInput { 12 | material: pbr_types__StandardMaterial, 13 | occlusion: f32, 14 | frag_coord: vec4, 15 | world_position: vec4, 16 | world_normal: vec3, 17 | N: vec3, 18 | V: vec3, 19 | is_orthographic: bool, 20 | } 21 | 22 | fn pbr_functions__pbr(in: pbr_functions__PbrInput) -> vec4 { 23 | var output_color_2: vec4 = in.material.base_color; 24 | 25 | if ((in.material.flags & 64u) != 0u) { 26 | output_color_2.w = 1.0; 27 | } else { 28 | if ((in.material.flags & 128u) != 0u) { 29 | let _e52: f32 = output_color_2.w; 30 | if (_e52 >= in.material.alpha_cutoff) { 31 | output_color_2.w = 1.0; 32 | } else { 33 | discard; 34 | } 35 | } 36 | } 37 | 38 | return output_color_2; 39 | } -------------------------------------------------------------------------------- /src/prune/tests/pbr_reduced.wgsl: -------------------------------------------------------------------------------- 1 | struct mesh_vertex_output__MeshVertexOutput { 2 | @location(0) world_position: vec4, 3 | @location(1) world_normal: vec3, 4 | @location(2) uv: vec2, 5 | } 6 | 7 | struct pbr_types__StandardMaterial { 8 | base_color: vec4, 9 | emissive: vec4, 10 | perceptual_roughness: f32, 11 | metallic: f32, 12 | reflectance: f32, 13 | flags: u32, 14 | alpha_cutoff: f32, 15 | } 16 | 17 | struct mesh_types__Mesh { 18 | model: mat4x4, 19 | inverse_transpose_model: mat4x4, 20 | flags: u32, 21 | } 22 | 23 | struct mesh_view_types__View { 24 | view_proj: mat4x4, 25 | inverse_view_proj: mat4x4, 26 | view: mat4x4, 27 | inverse_view: mat4x4, 28 | projection: mat4x4, 29 | inverse_projection: mat4x4, 30 | world_position: vec3, 31 | width: f32, 32 | height: f32, 33 | } 34 | 35 | struct mesh_view_types__PointLight { 36 | light_custom_data: vec4, 37 | color_inverse_square_range: vec4, 38 | position_radius: vec4, 39 | flags: u32, 40 | shadow_depth_bias: f32, 41 | shadow_normal_bias: f32, 42 | spot_light_tan_angle: f32, 43 | } 44 | 45 | struct mesh_view_types__DirectionalLight { 46 | view_projection: mat4x4, 47 | color: vec4, 48 | direction_to_light: vec3, 49 | flags: u32, 50 | shadow_depth_bias: f32, 51 | shadow_normal_bias: f32, 52 | } 53 | 54 | struct mesh_view_types__Lights { 55 | directional_lights: array, 56 | ambient_color: vec4, 57 | cluster_dimensions: vec4, 58 | cluster_factors: vec4, 59 | n_directional_lights: u32, 60 | spot_light_shadowmap_offset: i32, 61 | } 62 | 63 | struct mesh_view_types__PointLights { 64 | data: array, 65 | } 66 | 67 | struct mesh_view_types__ClusterLightIndexLists { 68 | data: array, 69 | } 70 | 71 | struct mesh_view_types__ClusterOffsetsAndCounts { 72 | data: array>, 73 | } 74 | 75 | struct pbr_functions__PbrInput { 76 | material: pbr_types__StandardMaterial, 77 | occlusion: f32, 78 | frag_coord: vec4, 79 | world_position: vec4, 80 | world_normal: vec3, 81 | N: vec3, 82 | V: vec3, 83 | is_orthographic: bool, 84 | } 85 | 86 | const pbr_types__STANDARD_MATERIAL_FLAGS_EMISSIVE_TEXTURE_BIT: u32 = 2u; 87 | 88 | const pbr_types__STANDARD_MATERIAL_FLAGS_UNLIT_BIT: u32 = 32u; 89 | 90 | const pbr_types__STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND: u32 = 256u; 91 | 92 | const pbr_types__STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP: u32 = 512u; 93 | 94 | const pbr_types__STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK: u32 = 128u; 95 | 96 | const pbr_types__STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT: u32 = 16u; 97 | 98 | const pbr_types__STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT: u32 = 1u; 99 | 100 | const pbr_types__STANDARD_MATERIAL_FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT: u32 = 4u; 101 | 102 | const pbr_types__STANDARD_MATERIAL_FLAGS_OCCLUSION_TEXTURE_BIT: u32 = 8u; 103 | 104 | const pbr_types__STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE: u32 = 64u; 105 | 106 | const pbr_types__STANDARD_MATERIAL_FLAGS_FLIP_NORMAL_MAP_Y: u32 = 1024u; 107 | 108 | const mesh_types__MESH_FLAGS_SHADOW_RECEIVER_BIT: u32 = 1u; 109 | 110 | const mesh_view_types__POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE: u32 = 2u; 111 | 112 | const mesh_view_types__DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u; 113 | 114 | const mesh_view_types__POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u; 115 | 116 | const utils__PI: f32 = 3.1415927410125732; 117 | 118 | const clustered_forward__CLUSTER_COUNT_SIZE: u32 = 9u; 119 | 120 | @group(2) @binding(0) 121 | var mesh_bindings__mesh: mesh_types__Mesh; 122 | @group(0) @binding(0) 123 | var mesh_view_bindings__view: mesh_view_types__View; 124 | @group(0) @binding(2) 125 | var mesh_view_bindings__point_shadow_textures: texture_depth_cube_array; 126 | @group(0) @binding(5) 127 | var mesh_view_bindings__directional_shadow_textures_sampler: sampler_comparison; 128 | @group(0) @binding(6) 129 | var mesh_view_bindings__point_lights: mesh_view_types__PointLights; 130 | @group(0) @binding(1) 131 | var mesh_view_bindings__lights: mesh_view_types__Lights; 132 | @group(0) @binding(3) 133 | var mesh_view_bindings__point_shadow_textures_sampler: sampler_comparison; 134 | @group(0) @binding(4) 135 | var mesh_view_bindings__directional_shadow_textures: texture_depth_2d_array; 136 | @group(0) @binding(7) 137 | var mesh_view_bindings__cluster_light_index_lists: mesh_view_types__ClusterLightIndexLists; 138 | @group(0) @binding(8) 139 | var mesh_view_bindings__cluster_offsets_and_counts: mesh_view_types__ClusterOffsetsAndCounts; 140 | @group(1) @binding(8) 141 | var pbr_bindings__occlusion_sampler: sampler; 142 | @group(1) @binding(0) 143 | var pbr_bindings__material: pbr_types__StandardMaterial; 144 | @group(1) @binding(3) 145 | var pbr_bindings__emissive_texture: texture_2d; 146 | @group(1) @binding(1) 147 | var pbr_bindings__base_color_texture: texture_2d; 148 | @group(1) @binding(5) 149 | var pbr_bindings__metallic_roughness_texture: texture_2d; 150 | @group(1) @binding(4) 151 | var pbr_bindings__emissive_sampler: sampler; 152 | @group(1) @binding(6) 153 | var pbr_bindings__metallic_roughness_sampler: sampler; 154 | @group(1) @binding(2) 155 | var pbr_bindings__base_color_sampler: sampler; 156 | @group(1) @binding(10) 157 | var pbr_bindings__normal_map_sampler: sampler; 158 | @group(1) @binding(9) 159 | var pbr_bindings__normal_map_texture: texture_2d; 160 | @group(1) @binding(7) 161 | var pbr_bindings__occlusion_texture: texture_2d; 162 | 163 | 164 | fn pbr_functions__pbr(in: pbr_functions__PbrInput) -> vec4 { 165 | var output_color_2: vec4; 166 | output_color_2 = in.material.base_color; 167 | 168 | if ((in.material.flags & pbr_types__STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE) != 0u) { 169 | output_color_2.w = 1.0; 170 | } else { 171 | if ((in.material.flags & pbr_types__STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK) != 0u) { 172 | let _e52: f32 = output_color_2.w; 173 | if (_e52 >= in.material.alpha_cutoff) { 174 | output_color_2.w = 1.0; 175 | } else { 176 | discard; 177 | } 178 | } 179 | } 180 | 181 | return output_color_2; 182 | } 183 | 184 | @fragment 185 | fn fragment(mesh: mesh_vertex_output__MeshVertexOutput, @builtin(front_facing) is_front: bool, @builtin(position) frag_coord: vec4) -> @location(0) vec4 { 186 | var output_color: vec4; 187 | var pbr_input: pbr_functions__PbrInput; 188 | 189 | output_color = pbr_bindings__material.base_color; 190 | 191 | if ((pbr_bindings__material.flags & pbr_types__STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u) { 192 | pbr_input.material.base_color = output_color; 193 | pbr_input.material.flags = pbr_bindings__material.flags; 194 | pbr_input.material.metallic = 15.0; // unused 195 | pbr_input.V = vec3(1.0, 2.0, 3.0); // unused 196 | pbr_input.material.alpha_cutoff = pbr_bindings__material.alpha_cutoff; 197 | 198 | output_color = pbr_functions__pbr(pbr_input); 199 | } 200 | return output_color; 201 | } 202 | -------------------------------------------------------------------------------- /src/prune/tests/test.wgsl: -------------------------------------------------------------------------------- 1 | fn expensive_subfunc(input: f32) -> f32 { 2 | return input + 1.5; 3 | } 4 | 5 | fn subfunc(input: f32) -> f32 { 6 | return input + 1.0; 7 | } 8 | 9 | fn test(input_one: f32, input_two: f32) -> vec2 { 10 | var res: vec2 = vec2(1.0, 1.0); 11 | 12 | // for(var i=0.0; i < input_two; i += 1.0) { 13 | // res.x += input_one; 14 | // res.y += input_two; 15 | // } 16 | 17 | // for(var i=0.0; i < input_two; i += 1.0) { 18 | // res.y += input_two; 19 | // } 20 | 21 | for(var i=0.0; i < input_two; i += 1.0) { 22 | res.y += input_one; 23 | } 24 | 25 | res.y += expensive_subfunc(1.0); 26 | res.x += subfunc(3.0); 27 | res = res.yx; 28 | 29 | return res; 30 | } -------------------------------------------------------------------------------- /src/redirect.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | 3 | use naga::{Block, Expression, Function, Handle, Module, Statement}; 4 | use thiserror::Error; 5 | 6 | use crate::derive::DerivedModule; 7 | 8 | #[derive(Debug, Error)] 9 | pub enum RedirectError { 10 | #[error("can't find function {0} for redirection")] 11 | FunctionNotFound(String), 12 | #[error("{0} cannot override {1} due to argument mismatch")] 13 | ArgumentMismatch(String, String), 14 | #[error("{0} cannot override {1} due to return type mismatch")] 15 | ReturnTypeMismatch(String, String), 16 | #[error("circular reference; can't find an order for : {0}")] 17 | CircularReference(String), 18 | } 19 | 20 | pub struct Redirector { 21 | module: Module, 22 | } 23 | 24 | impl Redirector { 25 | pub fn new(module: Module) -> Self { 26 | Self { module } 27 | } 28 | 29 | fn redirect_block(block: &mut Block, original: Handle, new: Handle) { 30 | for stmt in block.iter_mut() { 31 | match stmt { 32 | Statement::Call { 33 | ref mut function, .. 34 | } => { 35 | if *function == original { 36 | *function = new; 37 | } 38 | } 39 | Statement::Block(b) => Self::redirect_block(b, original, new), 40 | Statement::If { 41 | condition: _, 42 | accept, 43 | reject, 44 | } => { 45 | Self::redirect_block(accept, original, new); 46 | Self::redirect_block(reject, original, new); 47 | } 48 | Statement::Switch { selector: _, cases } => { 49 | for case in cases.iter_mut() { 50 | Self::redirect_block(&mut case.body, original, new); 51 | } 52 | } 53 | Statement::Loop { 54 | body, 55 | continuing, 56 | break_if: _, 57 | } => { 58 | Self::redirect_block(body, original, new); 59 | Self::redirect_block(continuing, original, new); 60 | } 61 | Statement::Emit(_) 62 | | Statement::Break 63 | | Statement::Continue 64 | | Statement::Return { .. } 65 | | Statement::WorkGroupUniformLoad { .. } 66 | | Statement::Kill 67 | | Statement::Barrier(_) 68 | | Statement::Store { .. } 69 | | Statement::ImageStore { .. } 70 | | Statement::Atomic { .. } 71 | | Statement::RayQuery { .. } 72 | | Statement::SubgroupBallot { .. } 73 | | Statement::SubgroupGather { .. } 74 | | Statement::SubgroupCollectiveOperation { .. } 75 | | Statement::ImageAtomic { .. } => (), 76 | } 77 | } 78 | } 79 | 80 | fn redirect_expr(expr: &mut Expression, original: Handle, new: Handle) { 81 | if let Expression::CallResult(f) = expr { 82 | if f == &original { 83 | *expr = Expression::CallResult(new); 84 | } 85 | } 86 | } 87 | 88 | fn redirect_fn(func: &mut Function, original: Handle, new: Handle) { 89 | Self::redirect_block(&mut func.body, original, new); 90 | for (_, expr) in func.expressions.iter_mut() { 91 | Self::redirect_expr(expr, original, new); 92 | } 93 | } 94 | 95 | /// redirect all calls to the function named `original` with references to the function named `replacement`, except within the replacement function 96 | /// or in any function contained in the `omit` set. 97 | /// returns handles to the original and replacement functions. 98 | /// NB: requires the replacement to be defined in the arena before any calls to the original, or validation will fail. 99 | pub fn redirect_function( 100 | &mut self, 101 | original: &str, 102 | replacement: &str, 103 | omit: &HashSet, 104 | ) -> Result<(Handle, Handle), RedirectError> { 105 | let (h_original, f_original) = self 106 | .module 107 | .functions 108 | .iter() 109 | .find(|(_, f)| f.name.as_deref() == Some(original)) 110 | .ok_or_else(|| RedirectError::FunctionNotFound(original.to_owned()))?; 111 | let (h_replacement, f_replacement) = self 112 | .module 113 | .functions 114 | .iter() 115 | .find(|(_, f)| f.name.as_deref() == Some(replacement)) 116 | .ok_or_else(|| RedirectError::FunctionNotFound(replacement.to_owned()))?; 117 | 118 | for (arg1, arg2) in f_original 119 | .arguments 120 | .iter() 121 | .zip(f_replacement.arguments.iter()) 122 | { 123 | if arg1.ty != arg2.ty { 124 | return Err(RedirectError::ArgumentMismatch( 125 | original.to_owned(), 126 | replacement.to_owned(), 127 | )); 128 | } 129 | } 130 | 131 | if f_original.result.as_ref().map(|r| r.ty) != f_replacement.result.as_ref().map(|r| r.ty) { 132 | return Err(RedirectError::ReturnTypeMismatch( 133 | original.to_owned(), 134 | replacement.to_owned(), 135 | )); 136 | } 137 | 138 | for (h_f, f) in self.module.functions.iter_mut() { 139 | if h_f != h_replacement && !omit.contains(f.name.as_ref().unwrap()) { 140 | Self::redirect_fn(f, h_original, h_replacement); 141 | } 142 | } 143 | 144 | for ep in &mut self.module.entry_points { 145 | Self::redirect_fn(&mut ep.function, h_original, h_replacement); 146 | } 147 | 148 | Ok((h_original, h_replacement)) 149 | } 150 | 151 | fn gather_requirements(block: &Block) -> HashSet> { 152 | let mut requirements = HashSet::default(); 153 | 154 | for stmt in block.iter() { 155 | match stmt { 156 | Statement::Block(b) => requirements.extend(Self::gather_requirements(b)), 157 | Statement::If { accept, reject, .. } => { 158 | requirements.extend(Self::gather_requirements(accept)); 159 | requirements.extend(Self::gather_requirements(reject)); 160 | } 161 | Statement::Switch { cases, .. } => { 162 | for case in cases { 163 | requirements.extend(Self::gather_requirements(&case.body)); 164 | } 165 | } 166 | Statement::Loop { 167 | body, continuing, .. 168 | } => { 169 | requirements.extend(Self::gather_requirements(body)); 170 | requirements.extend(Self::gather_requirements(continuing)); 171 | } 172 | Statement::Call { function, .. } => { 173 | requirements.insert(*function); 174 | } 175 | _ => (), 176 | } 177 | } 178 | 179 | requirements 180 | } 181 | 182 | pub fn into_module(self) -> Result { 183 | // reorder functions so that dependents come first 184 | let mut requirements: HashMap<_, _> = self 185 | .module 186 | .functions 187 | .iter() 188 | .map(|(h_f, f)| (h_f, Self::gather_requirements(&f.body))) 189 | .collect(); 190 | 191 | let mut derived = DerivedModule::default(); 192 | derived.set_shader_source(&self.module, 0); 193 | 194 | while !requirements.is_empty() { 195 | let start_len = requirements.len(); 196 | 197 | let mut added: HashSet> = HashSet::new(); 198 | 199 | // add anything that has all requirements satisfied 200 | requirements.retain(|h_f, reqs| { 201 | if reqs.is_empty() { 202 | let func = self.module.functions.try_get(*h_f).unwrap(); 203 | let span = self.module.functions.get_span(*h_f); 204 | derived.import_function(func, span); 205 | added.insert(*h_f); 206 | false 207 | } else { 208 | true 209 | } 210 | }); 211 | 212 | // remove things we added from requirements 213 | for reqs in requirements.values_mut() { 214 | reqs.retain(|req| !added.contains(req)); 215 | } 216 | 217 | if requirements.len() == start_len { 218 | return Err(RedirectError::CircularReference(format!( 219 | "{:#?}", 220 | requirements.keys() 221 | ))); 222 | } 223 | } 224 | 225 | Ok(derived.into_module_with_entrypoints()) 226 | } 227 | } 228 | 229 | impl TryFrom for naga::Module { 230 | type Error = RedirectError; 231 | 232 | fn try_from(redirector: Redirector) -> Result { 233 | redirector.into_module() 234 | } 235 | } 236 | --------------------------------------------------------------------------------