├── .config └── nextest.toml ├── .gitattributes ├── .github └── workflows │ ├── postsubmit-miri.yml │ ├── postsubmit.yml │ └── presubmit.yml ├── .gitignore ├── .gitmodules ├── .idx └── dev.nix ├── .mdformat.toml ├── .python-version ├── .typos.toml ├── Android.bp ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile.toml ├── Pipfile ├── Pipfile.lock ├── README.md ├── examples └── hello-world │ ├── Cargo.toml │ └── src │ ├── impl.rs │ └── lib.rs ├── pyproject.toml ├── rustfmt.toml ├── scripts ├── Cargo.toml ├── __init__.py ├── ci.rs ├── codegen.rs ├── lib.rs ├── list_files │ ├── main.rs │ └── mod.rs └── vulkan_layer_genvk │ ├── __init__.py │ ├── __main__.py │ ├── global_simple_intercept_generator.py │ ├── layer_trait_generator.py │ └── vk_xml_util.py ├── taplo.toml ├── vulkan-layer-macros ├── Cargo.toml └── src │ ├── details.rs │ ├── dummy.rs │ └── lib.rs ├── vulkan-layer ├── Android.bp ├── Cargo.toml ├── build.rs ├── src │ ├── bindings.rs │ ├── bindings │ │ ├── vk_layer.rs │ │ └── vk_layer │ │ │ ├── generated.rs │ │ │ └── generated │ │ │ ├── unix.rs │ │ │ └── windows.rs │ ├── global_simple_intercept.rs │ ├── global_simple_intercept │ │ └── generated.rs │ ├── layer_trait.rs │ ├── layer_trait │ │ └── generated.rs │ ├── lazy_collection.rs │ ├── lib.rs │ ├── test_utils.rs │ ├── test_utils │ │ ├── device_hooks_mock.rs │ │ ├── global_hooks_mock.rs │ │ └── instance_hooks_mock.rs │ ├── unstable_api.rs │ └── vk_utils.rs └── tests │ ├── autoinfo_macros_test.rs │ ├── declare_introspection_queries_test.rs │ ├── integration_test.rs │ └── utils │ └── mod.rs └── yamlfmt.yml /.config/nextest.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | [profile.default] 16 | retries = 0 17 | test-threads = "num-cpus" 18 | 19 | [profile.ci] 20 | fail-fast = false 21 | failure-output = "immediate-final" 22 | 23 | [profile.default-miri] 24 | test-threads = "num-cpus" 25 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto !eol 2 | -------------------------------------------------------------------------------- /.github/workflows/postsubmit-miri.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # For safety reasons, we have to pin a third-party github action to a specific commit hash. 15 | on: 16 | push: 17 | branches: ["main"] 18 | name: postsubmit miri 19 | jobs: 20 | miri: 21 | name: Rust Miri test 22 | # Miri test has the best support on x86_64-unknown-linux-gnu target, and the Linux runner is 23 | # generally faster. 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v4 27 | - name: Set up nightly Rust toolchain 28 | # dtolnay/rust-toolchain@nightly 29 | uses: dtolnay/rust-toolchain@f2f3c4b315c5bb8415dbb043af44ec90f68ae503 30 | with: 31 | components: | 32 | rust-src 33 | miri 34 | - name: Set up Rust dependency cache 35 | # Swatinem/rust-cache@2.7.8 36 | uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 37 | - name: Install cargo-make 38 | run: cargo install --no-default-features --locked cargo-make 39 | - name: Run tests with Miri 40 | run: cargo make miri 41 | -------------------------------------------------------------------------------- /.github/workflows/postsubmit.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | on: 15 | push: 16 | branches: ["main"] 17 | name: postsubmit 18 | jobs: 19 | run_presubmit: 20 | name: Run presubmit 21 | uses: ./.github/workflows/presubmit.yml 22 | upload_static_resources: 23 | name: Upload static resources 24 | needs: run_presubmit 25 | runs-on: ubuntu-latest 26 | permissions: 27 | contents: write 28 | pull-requests: write 29 | steps: 30 | - uses: actions/checkout@v4 31 | with: 32 | ref: static_resource 33 | - run: rm -rf coverage-Linux 34 | - name: Download Linux coverage artifacts 35 | uses: actions/download-artifact@v4 36 | with: 37 | name: coverage-Linux 38 | path: coverage-Linux 39 | - run: rm -rf coverage-Windows 40 | - name: Download Windows coverage artifacts 41 | uses: actions/download-artifact@v4 42 | with: 43 | name: coverage-Windows 44 | path: coverage-Windows 45 | - run: rm -rf doc-Linux 46 | - name: Download Linux doc artifacts 47 | uses: actions/download-artifact@v4 48 | with: 49 | name: doc-Linux 50 | path: doc-Linux 51 | - run: rm -rf doc-Windows 52 | - name: Download Windows doc artifacts 53 | uses: actions/download-artifact@v4 54 | with: 55 | name: doc-Windows 56 | path: doc-Windows 57 | - name: Commit files 58 | run: | 59 | git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" 60 | git config --local user.name "github-actions[bot]" 61 | git add . 62 | git commit -a -m "Upload artifacts for $GITHUB_SHA" 63 | - name: Push changes 64 | # ad-m/github-push-action@master 65 | uses: ad-m/github-push-action@77c5b412c50b723d2a4fbc6d71fb5723bcd439aa 66 | with: 67 | branch: static_resource 68 | -------------------------------------------------------------------------------- /.github/workflows/presubmit.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # For safety reasons, we have to pin a third-party github action to a specific commit hash. 15 | on: 16 | pull_request: 17 | branches: ["main"] 18 | workflow_call: {} 19 | name: presubmit 20 | jobs: 21 | buildtest: 22 | name: Rust build and test 23 | strategy: 24 | fail-fast: true 25 | matrix: 26 | os: ["ubuntu-latest", "windows-latest"] 27 | runs-on: ${{ matrix.os }} 28 | steps: 29 | - uses: actions/checkout@v4 30 | with: 31 | submodules: recursive 32 | - name: Install Linux dependency 33 | run: | 34 | wget http://security.ubuntu.com/ubuntu/pool/universe/n/ncurses/libtinfo5_6.3-2ubuntu0.1_amd64.deb 35 | sudo apt install ./libtinfo5_6.3-2ubuntu0.1_amd64.deb 36 | if: runner.os == 'Linux' 37 | - name: Setup LLVM install patrh 38 | id: setup-llvm-install-path 39 | run: | 40 | if [ "$RUNNER_OS" == "Linux" ]; then 41 | echo "LLVM_INSTALL_PATH=`realpath $GITHUB_WORKSPACE/../LLVM`" >> $GITHUB_OUTPUT 42 | elif [ "$RUNNER_OS" == "Windows" ]; then 43 | echo "LLVM_INSTALL_PATH=C:/Program Files/LLVM" >> $GITHUB_OUTPUT 44 | else 45 | echo "$RUNNER_OS not supported" 46 | exit 1 47 | fi 48 | shell: bash 49 | - name: Cache LLVM and Clang 50 | id: cache-llvm 51 | uses: actions/cache@v4 52 | with: 53 | path: ${{ steps.setup-llvm-install-path.outputs.LLVM_INSTALL_PATH }} 54 | key: ${{ format('llvm-16.0.0-{0}', runner.os) }} 55 | - name: Install LLVM and Clang 56 | # KyleMayes/install-llvm-action@v2.0.4 57 | uses: KyleMayes/install-llvm-action@10c8957324ef77f0712d883b9ed08adb6da4a192 58 | with: 59 | version: "16.0.0" 60 | directory: ${{ steps.setup-llvm-install-path.outputs.LLVM_INSTALL_PATH }} 61 | cached: ${{ steps.cache-llvm.outputs.cache-hit }} 62 | - name: Set LIBCLANG_PATH 63 | run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV 64 | if: runner.os == 'Windows' 65 | - name: Set up stable Rust toolchain 66 | # dtolnay/rust-toolchain@stable 67 | uses: dtolnay/rust-toolchain@4f366e621dc8fa63f557ca04b8f4361824a35a45 68 | with: 69 | components: clippy, llvm-tools-preview 70 | - name: Set up nightly Rust toolchain 71 | # dtolnay/rust-toolchain@nightly 72 | uses: dtolnay/rust-toolchain@f2f3c4b315c5bb8415dbb043af44ec90f68ae503 73 | with: 74 | components: rustfmt 75 | - run: rustup default stable 76 | - name: Set up Rust dependency cache 77 | # Swatinem/rust-cache@2.7.8 78 | uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 79 | with: 80 | key: ${{ runner.os }} 81 | cache-all-crates: true 82 | - name: Install cargo nextest 83 | run: cargo install cargo-nextest --locked 84 | - name: Install cargo-make 85 | run: cargo install --no-default-features --locked cargo-make 86 | - name: Install cargo llvm cov 87 | run: cargo install cargo-llvm-cov --locked 88 | - uses: actions/setup-python@v5 89 | with: 90 | python-version: "3.13t" 91 | cache: "pipenv" 92 | - name: Install pipenv 93 | run: python -m pip install pipenv==2025.0.2 94 | - name: Install python dependencies 95 | run: pipenv install --ignore-pipfile 96 | - name: Install addlicense 97 | run: go install github.com/google/addlicense@master 98 | - run: cargo make ci-presubmit 99 | - name: Checkout base coverage 100 | uses: actions/checkout@v4 101 | with: 102 | ref: static_resource 103 | sparse-checkout: | 104 | ${{ format('coverage-{0}/lcov.info', runner.os) }} 105 | sparse-checkout-cone-mode: false 106 | path: target/base-coverage 107 | - name: Report coverage 108 | # 06393993/lcov-reporter-action@master 109 | # TODO: once the upstream accept the PR, change back to romeovs/lcov-reporter-action@master 110 | uses: 06393993/lcov-reporter-action@24d48ff28930b87e67d0df34283153b04a76f166 111 | with: 112 | lcov-file: target/lcov.info 113 | lcov-base: ${{ format('target/base-coverage/coverage-{0}/lcov.info', runner.os) }} 114 | filter-changed-files: true 115 | title: ${{ format('Test coverage for {0}', runner.os) }} 116 | post-to: job-summary 117 | - uses: actions/upload-artifact@v4 118 | with: 119 | name: ${{ format('coverage-{0}', runner.os) }} 120 | # lcov.info is needed for the next commit and PR to diff the coverage report of the base 121 | # HTML results are needed for human to check the coverage report 122 | # The coverage badge is used in the README.md. 123 | path: | 124 | target/lcov.info 125 | target/llvm-cov 126 | target/coverage_badge.json 127 | - uses: actions/upload-artifact@v4 128 | with: 129 | name: ${{ format('doc-{0}', runner.os) }} 130 | path: target/doc 131 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 3 | .vscode 4 | __pycache__ 5 | *.pyc 6 | .ruff_cache 7 | 8 | venv/ 9 | 10 | .jj/ 11 | .python-version 12 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/Vulkan-Headers"] 2 | path = third_party/Vulkan-Headers 3 | url = https://github.com/KhronosGroup/Vulkan-Headers.git -------------------------------------------------------------------------------- /.idx/dev.nix: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | { pkgs, ... }: { 16 | channel = "stable-23.11"; 17 | env = 18 | let 19 | getRustBindgenEnv = env-name: 20 | let 21 | runCommand = (import { }).runCommand; 22 | fileContents = (import { }).lib.strings.fileContents; 23 | setupScriptPath = "${pkgs.rustPlatform.bindgenHook}/nix-support/setup-hook"; 24 | store-path = runCommand 25 | "get-rust-bindgen-${env-name}" 26 | { } 27 | ". ${setupScriptPath} && $postHook && echo \$${env-name} >$out"; 28 | command-output-str = fileContents (builtins.toPath store-path); 29 | in 30 | command-output-str; 31 | in 32 | { 33 | BINDGEN_EXTRA_CLANG_ARGS = getRustBindgenEnv "BINDGEN_EXTRA_CLANG_ARGS"; 34 | LIBCLANG_PATH = getRustBindgenEnv "LIBCLANG_PATH"; 35 | }; 36 | packages = [ 37 | pkgs.clang 38 | pkgs.libclang 39 | pkgs.llvmPackages.libllvm 40 | pkgs.llvmPackages.stdenv 41 | pkgs.llvmPackages.libcxxStdenv 42 | pkgs.llvmPackages.libcxx 43 | pkgs.llvmPackages.libcxxClang 44 | pkgs.llvmPackages.bintools 45 | pkgs.python39 46 | pkgs.rustup 47 | pkgs.cargo-nextest 48 | pkgs.cargo-make 49 | pkgs.git 50 | pkgs.pipenv 51 | pkgs.go 52 | pkgs.taplo 53 | ]; 54 | idx = { 55 | extensions = [ 56 | "rust-lang.rust-analyzer" 57 | "ms-python.python" 58 | "ms-python.debugpy" 59 | "ms-python.black-formatter" 60 | "tamasfe.even-better-toml" 61 | ]; 62 | workspace = { 63 | onCreate = { 64 | git-clone-submodules = "git submodule update --init --recursive"; 65 | rust-toolchain-stable-install = "rustup toolchain install stable"; 66 | rust-toolchain-nightly-install = 67 | "rustup toolchain install nightly --component miri rustfmt rust-src --profile minimal"; 68 | pipenv-install = "pipenv install --ignore-pipfile"; 69 | addlicense-install = "go install github.com/google/addlicense@master"; 70 | }; 71 | }; 72 | }; 73 | } 74 | -------------------------------------------------------------------------------- /.mdformat.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | end_of_line = "keep" 16 | wrap = 100 17 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.13 2 | -------------------------------------------------------------------------------- /.typos.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | [files] 16 | extend-exclude = ["third_party"] 17 | ignore-vcs = true 18 | 19 | [default.extend-words] 20 | # LOD refers to level of details in graphics. 21 | lod = "lod" 22 | 23 | [default.extend-identifiers] 24 | # Don't correct the spelling existing in the Vulkan upstream. 25 | LAYER_NEGOTIATE_UNINTIALIZED = "LAYER_NEGOTIATE_UNINTIALIZED" 26 | -------------------------------------------------------------------------------- /Android.bp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package { 16 | default_applicable_licenses: ["external_nexus_rust_vk_layer_license"], 17 | } 18 | 19 | license { 20 | name: "external_nexus_rust_vk_layer_license", 21 | visibility: [ 22 | ":__subpackages__", 23 | ], 24 | license_kinds: [ 25 | "SPDX-license-identifier-Apache-2.0", 26 | ], 27 | } 28 | 29 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We would love to accept your patches and contributions to this project. 4 | 5 | ## Before you begin 6 | 7 | ### Sign our Contributor License Agreement 8 | 9 | Contributions to this project must be accompanied by a 10 | [Contributor License Agreement](https://cla.developers.google.com/about) (CLA). You (or your 11 | employer) retain the copyright to your contribution; this simply gives us permission to use and 12 | redistribute your contributions as part of the project. 13 | 14 | If you or your current employer have already signed the Google CLA (even if it was for a different 15 | project), you probably don't need to do it again. 16 | 17 | Visit to see your current agreements or to sign a new one. 18 | 19 | ### Review our Community Guidelines 20 | 21 | This project follows 22 | [Google's Open Source Community Guidelines](https://opensource.google/conduct/). 23 | 24 | ## Contribution process 25 | 26 | ### Prerequisites 27 | 28 | - Install [cargo-make](https://sagiegurari.github.io/cargo-make/#installation). 29 | - Install [pipenv](https://pipenv.pypa.io/en/latest/#install-pipenv-today). 30 | - Install Python dependencies: `pipenv install`. 31 | 32 | ### Code Reviews 33 | 34 | All submissions, including submissions by project members, require review. We use 35 | [GitHub pull requests](https://docs.github.com/articles/about-pull-requests) for this purpose. 36 | 37 | ### Format the code 38 | 39 | Presubmit will fail if the code is not properly format. To run all lints and formatters, run 40 | `cargo make lint`. 41 | 42 | #### Rust 43 | 44 | Nightly features in `rustfmt` is used, so run `cargo +nightly fmt` to format the entire project. 45 | 46 | To run both rustfmt and clippy, run `cargo make rust-lint`. 47 | 48 | #### Python 49 | 50 | Python scripts are used to generate some Rust source files. Use 51 | [black](https://black.readthedocs.io/en/stable) to format python code: 52 | 53 | ```bash 54 | $ pipenv run black . 55 | All done! ✨ 🍰 ✨ 56 | 4 files left unchanged. 57 | ``` 58 | 59 | Use [ruff](https://github.com/astral-sh/ruff) to lint the python scripts: 60 | 61 | ```bash 62 | pipenv run ruff check . --fix 63 | ``` 64 | 65 | To run all python formatters and lints, run `cargo make python-lint`. 66 | 67 | #### TOML 68 | 69 | [taplo](https://taplo.tamasfe.dev/) is used to format all TOML files. 70 | 71 | Use the `toml-fmt` cargo-make target to run it, and it will also handle the end of lines for 72 | different platforms. 73 | 74 | ```bash 75 | cargo make toml-fmt 76 | ``` 77 | 78 | We can also just use `taplo`. One must take care of end of lines on Windows. 79 | 80 | ```bash 81 | taplo fmt 82 | ``` 83 | 84 | on Windows 85 | 86 | ```bash 87 | taplo fmt --option clrf=true 88 | ``` 89 | 90 | ### Regenerate code 91 | 92 | Files named `generated.rs` are generated files. One should avoid manually editing the files. 93 | Presubmit will fail if the generated files are not sync with the source. 94 | 95 | 1. Install LLVM and set up the environment variable required by `bindgen`: 96 | [link](https://rust-lang.github.io/rust-bindgen/requirements.html), because `bindgen` is used in 97 | the codegen process. 98 | 99 | 1. Make sure python newer than 3.13 is installed. 100 | 101 | 1. Run the following command: 102 | 103 | ```bash 104 | cargo make codegen 105 | ``` 106 | 107 | The codegen scripts will automatically format the generated file, so no need to manually format the 108 | project after running codegen. 109 | 110 | A separate Rust binary `scripts/codegen.rs` is used to automate the code generation process. 111 | 112 | Files are generated in different mechanism: 113 | 114 | - `vulkan-layer/src/bindings/vk_layer/generated.rs` 115 | 116 | This file is generated from `vk_layer.h` in the `Vulkan-Headers` dependency by using `bindgen`. 117 | 118 | - `vulkan-layer/src/{layer_trait,global_simple_intercept}/generated.rs` 119 | 120 | These 2 files are generated through the `scritps/vulkan_layer_genvk.py`. This script utilizes the 121 | `OutputGenerator` class defined in the `generator.py` from the Vulkan registry repo. 122 | 123 | ### Test 124 | 125 | All tests mentioned are run in CI. Miri tests are run as a post submit because of the slowness while 126 | others are presubmit. 127 | 128 | #### Unit tests and integration tests 129 | 130 | `cargo-nextest` is the recommended way to run the test. Run `cargo nextest run` or `cargo make test` 131 | to run all tests. This is how CI runs the tests. All tests are supposed to only run in a separate 132 | process. 133 | 134 | Vanilla `cargo test` is not supported, because that will run tests in the same binary in the same 135 | process. However, `cargo +nightly test -Z panic-abort-tests` is Ok because it will 136 | [allow subprocess testing](https://github.com/rust-lang/rust/issues/67650). In addition, the library 137 | is supposed to be built with `panic="abort"`. 138 | 139 | #### Documentation tests 140 | 141 | ```bash 142 | cargo make doctest 143 | ``` 144 | 145 | or 146 | 147 | ```bash 148 | cargo +nightly test --doc --all-features --workspace -- --show-output 149 | ``` 150 | 151 | #### Coverage 152 | 153 | Code coverage is also part of the CI. Changes shouldn't cause the coverage to drop. Newly added 154 | functions or modules are expected high test coverage. 155 | 156 | [`cargo-llvm-cov`](https://github.com/taiki-e/cargo-llvm-cov) 157 | [with `cargo-nextest`](https://nexte.st/book/test-coverage.html#llvm-cov) is used to generate the 158 | code coverage info. To generate the `lcov` coverage file locally under `target/lcov.info`, run 159 | 160 | ```bash 161 | cargo make coverage --lcov --output-path target/lcov.info 162 | ``` 163 | 164 | ```bash 165 | cargo llvm-cov nextest --all-features --all-targets --lcov --output-path target/lcov.info 166 | ``` 167 | 168 | #### Miri tests 169 | 170 | When writing unsafe code or changing the "trivially-destructible" behavior of the global resources, 171 | Miri tests should be used to catch undefined behaviors and resource leak. It is recommended to run 172 | Miri tests on Linux. 173 | 174 | To run miri tests, simply execute `cargo make miri`, which should install all the missing 175 | dependencies and run the tests. 176 | 177 | To manually run the miri tests, follow [these steps](https://github.com/rust-lang/miri#using-miri) 178 | to install Miri. Use the following command to run the Miri test on Linux: 179 | 180 | ```bash 181 | MIRIFLAGS="-Zmiri-tree-borrows" cargo +nightly miri nextest run --all-targets --all-features -j8 --no-fail-fast 182 | ``` 183 | 184 | Miri tests are usually very slow, use the 185 | [test filter](https://nexte.st/book/filter-expressions.html) to run specific tests. 186 | 187 | ```bash 188 | MIRIFLAGS="-Zmiri-tree-borrows" cargo +nightly miri nextest run --all-targets --all-features --no-fail-fast -E'test(test_should_return_fp_when_called_with_get_instance_proc_addr_name)' 189 | ``` 190 | 191 | ### Document 192 | 193 | All public interfaces must have documents. Otherwise the presubmit will fail. After a new PR is 194 | merged, the document will be automatically regenerated and published. Use the badge in 195 | [`README.md`](README.md) to browse the document. To generate the document locally, run 196 | 197 | ```bash 198 | cargo make doc --open 199 | ``` 200 | 201 | or 202 | 203 | ```bash 204 | cargo +nightly doc --workspace --all-features --no-deps --open 205 | ``` 206 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | [workspace] 16 | members = ["vulkan-layer", "vulkan-layer-macros", "examples/hello-world", "scripts"] 17 | resolver = "2" 18 | # Do not operate on vulkan-layer-macros by default, because it doesn't work well with panic=abort 19 | # for testing. 20 | default-members = ["vulkan-layer"] 21 | 22 | [profile.dev] 23 | panic = "abort" 24 | 25 | [profile.dev.build-override] 26 | opt-level = 3 27 | 28 | [profile.release] 29 | panic = "abort" 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | [config] 16 | default_to_workspace = false 17 | skip_core_tasks = true 18 | unstable_features = ["CTRL_C_HANDLING"] 19 | 20 | [tasks.init] 21 | # Should not use env_scripts which will also be executed by child cargo-make tasks. 22 | description = "Set up environements." 23 | private = true 24 | script = ''' 25 | 26 | # Check whether linters and formatters should only check or fix in place. 27 | echo Parsing arguments... 28 | args = split ${CARGO_MAKE_TASK_ARGS} ; 29 | args = set_from_array ${args} 30 | 31 | # Handle the arguments 32 | has_check_arg = set_contains ${args} --check 33 | echo Check if --check is passed to the argument: ${has_check_arg} 34 | cargo_make_task = get_env CARGO_MAKE_TASK 35 | is_ci_target = starts_with ${cargo_make_task} ci- 36 | echo Check if the ${cargo_make_task} target is a CI target: ${is_ci_target} 37 | if ${has_check_arg} or ${is_ci_target} 38 | set_env MY_CARGO_MAKE_BLACK_CHECK_ARG --check 39 | set_env MY_CARGO_MAKE_RUSTFMT_CHECK_ARG --check 40 | set_env MY_CARGO_MAKE_ADDLICENSE_CHECK_ARG -check 41 | set_env MY_CARGO_MAKE_CODEGEN_CHECK_ARG --check 42 | set_env MY_CARGO_MAKE_TAPLO_CHECK_ARG --check 43 | set_env MY_CARGO_MAKE_MDFORMAT_CHECK_ARG --check 44 | set_env MY_CARGO_MAKE_YAMLFMT_CHECK_ARGS -quiet;-lint 45 | else 46 | set_env MY_CARGO_MAKE_CLIPPY_FIX_ARGS --fix;--allow-dirty;--allow-staged 47 | set_env MY_CARGO_MAKE_RUFF_FIX_ARG --fix 48 | end 49 | 50 | # For some targets, only passthrough the arguments if it's the toplevel target. 51 | is_test_toplevel_target = eq ${cargo_make_task} test 52 | if ${is_test_toplevel_target} 53 | set_env MY_CARGO_MAKE_NEXTEST_USER_ARGS ${CARGO_MAKE_TASK_ARGS} 54 | end 55 | is_typos_toplevel_target = eq ${cargo_make_task} typos 56 | if ${is_typos_toplevel_target} 57 | set_env MY_CARGO_MAKE_TYPOS_USER_ARGS ${CARGO_MAKE_TASK_ARGS} 58 | end 59 | 60 | # Set up envs specific to CI 61 | if ${is_ci_target} 62 | # Use the ci profile for nextest 63 | set_env MY_CARGO_MAKE_NEXTEST_PROFILE_ARGS --profile;ci 64 | 65 | # Set up the -D warnings rustc flags 66 | rust_flags = get_env RUSTFLAGS 67 | if not is_empty rust_flags 68 | rust_flags = concat ${rust_flags} " " 69 | end 70 | rust_flags = concat ${rust_flags} "-D warnings" 71 | set_env RUSTFLAGS ${rust_flags} 72 | end 73 | ''' 74 | script_runner = "@duckscript" 75 | 76 | [tasks.coverage] 77 | args = ["llvm-cov", "nextest", "--all-features", "--all-targets", "${@}"] 78 | category = "Development" 79 | command = "cargo" 80 | dependencies = ["nextest-detection"] 81 | install_crate_args = ["--locked"] 82 | 83 | [tasks.codegen] 84 | # TODO: Detect if LLVM is correctly installed. If not, guide the user to install. 85 | args = [ 86 | "run", 87 | "--all-features", 88 | "--release", 89 | "-p", 90 | "vulkan-layer-scripts", 91 | "--bin", 92 | "codegen", 93 | "--", 94 | "@@remove-empty(MY_CARGO_MAKE_CODEGEN_CHECK_ARG)", 95 | ] 96 | category = "Development" 97 | command = "cargo" 98 | description = "Runs the codegen." 99 | 100 | [tasks.detect-addlicense] 101 | private = true 102 | script = ''' 103 | echo Detecting the GOPATH environment... 104 | output = exec --fail-on-error go env GOPATH 105 | gopath = trim ${output.stdout} 106 | echo GOPATH=${gopath} 107 | addlicense_file_name = set addlicense 108 | if is_windows 109 | addlicense_file_name = concat ${addlicense_file_name} .exe 110 | end 111 | addlicense_path = join_path ${gopath} bin ${addlicense_file_name} 112 | echo Path of addlicense is ${addlicense_path} 113 | set_env MY_CARGO_MAKE_ADDLICENSE_PATH ${addlicense_path} 114 | addlicense_installed = is_path_exists ${addlicense_path} 115 | if not ${addlicense_installed} 116 | echo Install addlicense. 117 | exec --fail-on-error go install github.com/google/addlicense@master 118 | end 119 | ''' 120 | script_runner = "@duckscript" 121 | 122 | [tasks.license] 123 | # We shouldn't use the -ignore parameter, as that doesn't work on Windows: 124 | # https://github.com/google/addlicense/pull/157. 125 | args = [ 126 | "run", 127 | "-p", 128 | "vulkan-layer-scripts", 129 | "--bin", 130 | "list-files", 131 | "--release", 132 | "--", 133 | "--use-gitignore", 134 | "--ignore=**/third_party", 135 | ".", 136 | "--", 137 | "${MY_CARGO_MAKE_ADDLICENSE_PATH}", 138 | "@@remove-empty(MY_CARGO_MAKE_ADDLICENSE_CHECK_ARG)", 139 | ] 140 | category = "Format/Lint" 141 | command = "cargo" 142 | dependencies = ["detect-addlicense"] 143 | description = "Add license to source files." 144 | 145 | [tasks.rust-check] 146 | args = ["check", "--workspace", "--all-targets", "--all-features"] 147 | category = "Development" 148 | command = "cargo" 149 | description = "Runs cargo check." 150 | 151 | [tasks.rust-clippy] 152 | args = [ 153 | "clippy", 154 | "--workspace", 155 | "--all-targets", 156 | "--all-features", 157 | "@@split(MY_CARGO_MAKE_CLIPPY_FIX_ARGS,;)", 158 | ] 159 | category = "Format/Lint" 160 | command = "cargo" 161 | description = "Runs cargo clippy." 162 | 163 | [tasks.rust-fmt] 164 | args = ["fmt", "@@remove-empty(MY_CARGO_MAKE_RUSTFMT_CHECK_ARG)"] 165 | category = "Format/Lint" 166 | command = "cargo" 167 | description = "Runs cargo fmt." 168 | toolchain = "nightly" 169 | 170 | [tasks.rust-lint] 171 | category = "Format/Lint" 172 | dependencies = ["rust-fmt", "rust-check", "rust-clippy"] 173 | description = "Lint Rust source files." 174 | 175 | [tasks.python-ruff] 176 | args = ["run", "ruff", "check", "@@remove-empty(MY_CARGO_MAKE_RUFF_FIX_ARG)", "."] 177 | category = "Format/Lint" 178 | command = "pipenv" 179 | description = "Runs python ruff linter." 180 | private = true 181 | 182 | [tasks.python-black] 183 | args = ["run", "black", "@@remove-empty(MY_CARGO_MAKE_BLACK_CHECK_ARG)", "."] 184 | category = "Format/Lint" 185 | command = "pipenv" 186 | description = "Runs python black formatter." 187 | private = true 188 | 189 | [tasks.python-lint] 190 | category = "Format/Lint" 191 | dependencies = ["python-ruff", "python-black"] 192 | description = "Lint python source files." 193 | 194 | [tasks.toml-fmt] 195 | args = [ 196 | "fmt", 197 | "@@remove-empty(MY_CARGO_MAKE_TAPLO_FMT_CRLF_ARG)", 198 | "@@remove-empty(MY_CARGO_MAKE_TAPLO_CHECK_ARG)", 199 | ] 200 | category = "Format/Lint" 201 | command = "taplo" 202 | description = "Format toml files." 203 | install_crate.binary = "taplo" 204 | install_crate.crate_name = "taplo-cli" 205 | install_crate.test_arg = "--version" 206 | install_crate_args = ["--locked"] 207 | 208 | [tasks.toml-fmt.env.MY_CARGO_MAKE_TAPLO_FMT_CRLF_ARG] 209 | condition.platforms = ["windows"] 210 | value = "--option=crlf=true" 211 | 212 | [tasks.typos] 213 | args = ["@@split(MY_CARGO_MAKE_TYPOS_USER_ARGS,;)"] 214 | category = "Format/Lint" 215 | command = "typos" 216 | description = "Check spelling mistakes." 217 | install_crate.binary = "typos" 218 | install_crate.crate_name = "typos-cli" 219 | install_crate.test_arg = "--version" 220 | install_crate_args = ["--locked"] 221 | 222 | [tasks.mdformat] 223 | args = [ 224 | "run", 225 | "-p", 226 | "vulkan-layer-scripts", 227 | "--bin", 228 | "list-files", 229 | "--release", 230 | "--", 231 | "--use-gitignore", 232 | "--ignore=**/third_party", 233 | "--include=*.md", 234 | ".", 235 | "--", 236 | "pipenv", 237 | "run", 238 | "mdformat", 239 | "@@remove-empty(MY_CARGO_MAKE_MDFORMAT_CHECK_ARG)", 240 | ] 241 | category = "Format/Lint" 242 | command = "cargo" 243 | description = "Format the md files." 244 | 245 | [tasks.detect-yamlfmt] 246 | private = true 247 | script = ''' 248 | echo Detecting the GOPATH environment... 249 | output = exec --fail-on-error go env GOPATH 250 | gopath = trim ${output.stdout} 251 | echo GOPATH=${gopath} 252 | yamlfmt_file_name = set yamlfmt 253 | if is_windows 254 | yamlfmt_file_name = concat ${yamlfmt_file_name} .exe 255 | end 256 | yamlfmt_path = join_path ${gopath} bin ${yamlfmt_file_name} 257 | echo Path of yamlfmt is ${yamlfmt_path} 258 | set_env MY_CARGO_MAKE_YAMLFMT_PATH ${yamlfmt_path} 259 | yamlfmt_installed = is_path_exists ${yamlfmt_path} 260 | if not ${yamlfmt_installed} 261 | echo Install yamlfmt. 262 | exec --fail-on-error go install github.com/google/yamlfmt/cmd/yamlfmt@latest 263 | end 264 | ''' 265 | script_runner = "@duckscript" 266 | 267 | [tasks.yamlfmt] 268 | args = ["@@split(MY_CARGO_MAKE_YAMLFMT_CHECK_ARGS,;)", "-dstar", "**/*.{yaml,yml}"] 269 | category = "Format/Lint" 270 | command = "${MY_CARGO_MAKE_YAMLFMT_PATH}" 271 | condition.fail_message = """ 272 | on Windows, yamlfmt doesn't work well with comments in tables. See 273 | https://github.com/google/yamlfmt/issues/192 for details.""" 274 | condition.platforms = ["linux"] 275 | dependencies = ["detect-yamlfmt"] 276 | description = "Format YAML files." 277 | 278 | [tasks.lint] 279 | # No need to run lint tasks in parallel as the overhead of fork in cargo-make is significant. In the 280 | # IDX environment, with everything cached running in parallel takes 15s, while without parallel, it 281 | # takes less than 10s. 282 | category = "Format/Lint" 283 | dependencies = ["license", "toml-fmt"] 284 | description = "Lint all source files." 285 | run_task.name = ["rust-lint", "python-lint", "mdformat", "yamlfmt"] 286 | 287 | [tasks.nextest-detection] 288 | args = ["nextest", "--version"] 289 | command = "cargo" 290 | install_crate_args = ["--locked"] 291 | private = true 292 | 293 | [tasks.python-test] 294 | args = ["-m", "unittest", "discover", "-p", "*.py"] 295 | category = "Development" 296 | command = "python" 297 | description = "Runs python tests." 298 | 299 | [tasks.rust-test] 300 | args = [ 301 | "nextest", 302 | "run", 303 | "--workspace", 304 | "--all-targets", 305 | "--all-features", 306 | "@@split(MY_CARGO_MAKE_NEXTEST_PROFILE_ARGS,;)", 307 | "@@split(MY_CARGO_MAKE_NEXTEST_USER_ARGS,;)", 308 | ] 309 | category = "Development" 310 | command = "cargo" 311 | dependencies = ["nextest-detection"] 312 | description = "Runs cargo nextest." 313 | 314 | [tasks.test] 315 | category = "Development" 316 | dependencies = ["rust-test", "python-test"] 317 | description = "Runs tests." 318 | 319 | [tasks.doctest] 320 | args = ["+nightly", "test", "--doc", "--all-features", "--workspace", "--", "--show-output"] 321 | category = "Development" 322 | command = "cargo" 323 | description = "Runs Rust doc test." 324 | install_crate = false 325 | 326 | [tasks.doc] 327 | args = ["+nightly", "doc", "--workspace", "--all-features", "--no-deps", "${@}"] 328 | category = "Development" 329 | command = "cargo" 330 | description = "Generates Rust doc." 331 | install_crate = false 332 | 333 | [tasks.install-rust-src-nightly] 334 | install_crate = { rustup_component_name = "rust-src" } 335 | private = true 336 | toolchain = "nightly" 337 | 338 | [tasks.miri] 339 | args = [ 340 | "miri", 341 | "nextest", 342 | "run", 343 | "--all-targets", 344 | "--all-features", 345 | "--no-fail-fast", 346 | "--failure-output", 347 | "immediate-final", 348 | "${@}", 349 | ] 350 | category = "Development" 351 | command = "cargo" 352 | dependencies = ["nextest-detection", "install-rust-src-nightly"] 353 | description = "Runs tests with miri." 354 | env = { MIRIFLAGS = "-Zmiri-tree-borrows" } 355 | install_crate.binary = "cargo" 356 | install_crate.rustup_component_name = "miri" 357 | install_crate.test_arg = ["miri", "--version"] 358 | toolchain = "nightly" 359 | 360 | [tasks.ci-coverage-lcov-info] 361 | args = [ 362 | "llvm-cov", 363 | "nextest", 364 | "--all-features", 365 | "--all-targets", 366 | "--lcov", 367 | "--output-path", 368 | "target/lcov.info", 369 | ] 370 | category = "CI" 371 | command = "cargo" 372 | dependencies = ["nextest-detection"] 373 | description = "Runs code coverage and generate the lcov.info output." 374 | install_crate_args = ["--locked"] 375 | 376 | [tasks.ci-coverage-html] 377 | args = ["llvm-cov", "nextest", "--html", "--all-features", "--all-targets"] 378 | category = "CI" 379 | command = "cargo" 380 | dependencies = ["nextest-detection"] 381 | description = "Runs code coverage and generate the HTML output." 382 | install_crate_args = ["--locked"] 383 | 384 | [tasks.ci-coverage-summary-json] 385 | args = [ 386 | "llvm-cov", 387 | "nextest", 388 | "--all-features", 389 | "--all-targets", 390 | "--json", 391 | "--summary-only", 392 | "--output-path", 393 | "target/coverage.json", 394 | ] 395 | category = "CI" 396 | command = "cargo" 397 | dependencies = ["nextest-detection"] 398 | description = "Runs code coverage and generate the JSON summary." 399 | install_crate_args = ["--locked"] 400 | 401 | [tasks.ci-coverage-badge] 402 | args = [ 403 | "run", 404 | "--all-features", 405 | "--release", 406 | "-p", 407 | "vulkan-layer-scripts", 408 | "--bin", 409 | "ci", 410 | "--", 411 | "--label", 412 | "${MY_CARGO_MAKE_COVERAGE_BADGE_LABEL}", 413 | "--output-path", 414 | "target/coverage_badge.json", 415 | "target/coverage.json", 416 | ] 417 | category = "CI" 418 | command = "cargo" 419 | dependencies = ["ci-coverage-summary-json"] 420 | description = "Generate the code coverage badge." 421 | 422 | [tasks.ci-coverage-badge.env.MY_CARGO_MAKE_COVERAGE_BADGE_LABEL] 423 | default_value = "Unknown coverage" 424 | mapping.linux = "Linux coverage" 425 | mapping.windows = "Windows coverage" 426 | source = "${CARGO_MAKE_RUST_TARGET_OS}" 427 | 428 | [tasks.ci-coverage] 429 | # Must run the HTML coverage last. Otherwise other commands can clear this directory. 430 | run_task.name = ["ci-coverage-badge", "ci-coverage-lcov-info", "ci-coverage-html"] 431 | 432 | [tasks.buildtest] 433 | category = "Development" 434 | description = "Build the project and tests. Pass --check to avoid linter to modify files." 435 | run_task = { name = ["typos", "lint", "test", "doctest"], fork = true } 436 | 437 | [tasks.ci-buildtest] 438 | category = "CI" 439 | description = "Build the project and tests with the CI configuration." 440 | run_task.name = ["buildtest"] 441 | 442 | [tasks.ci-presubmit] 443 | category = "CI" 444 | description = "Runs the CI presubmit check." 445 | run_task.fork = true 446 | run_task.name = ["ci-buildtest", "ci-coverage", "codegen", "doc"] 447 | run_task.parallel = true 448 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | black = "*" 8 | ruff = "*" 9 | mdformat-gfm = "*" 10 | 11 | [dev-packages] 12 | 13 | [requires] 14 | python_version = "3.13" 15 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "154e16e8282756c257e5cbeeb54f165099b853180a080aabc1059e07c1550058" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.13" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "black": { 20 | "hashes": [ 21 | "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", 22 | "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7", 23 | "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da", 24 | "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", 25 | "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", 26 | "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", 27 | "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", 28 | "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", 29 | "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32", 30 | "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", 31 | "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", 32 | "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", 33 | "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0", 34 | "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", 35 | "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", 36 | "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", 37 | "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355", 38 | "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", 39 | "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e", 40 | "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9", 41 | "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", 42 | "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f" 43 | ], 44 | "index": "pypi", 45 | "markers": "python_version >= '3.9'", 46 | "version": "==25.1.0" 47 | }, 48 | "click": { 49 | "hashes": [ 50 | "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", 51 | "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a" 52 | ], 53 | "markers": "python_version >= '3.7'", 54 | "version": "==8.1.8" 55 | }, 56 | "colorama": { 57 | "hashes": [ 58 | "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", 59 | "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" 60 | ], 61 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", 62 | "version": "==0.4.6" 63 | }, 64 | "importlib-metadata": { 65 | "hashes": [ 66 | "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", 67 | "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd" 68 | ], 69 | "markers": "python_version >= '3.9'", 70 | "version": "==8.7.0" 71 | }, 72 | "markdown-it-py": { 73 | "hashes": [ 74 | "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", 75 | "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb" 76 | ], 77 | "markers": "python_version >= '3.8'", 78 | "version": "==3.0.0" 79 | }, 80 | "mdformat": { 81 | "hashes": [ 82 | "sha256:61122637c9e1d9be1329054f3fa216559f0d1f722b7919b060a8c2a4ae1850e5", 83 | "sha256:eef84fa8f233d3162734683c2a8a6222227a229b9206872e6139658d99acb1ea" 84 | ], 85 | "markers": "python_version >= '3.9'", 86 | "version": "==0.7.22" 87 | }, 88 | "mdformat-gfm": { 89 | "hashes": [ 90 | "sha256:63c92cfa5102f55779d4e04b16a79a6a5171e658c6c479175c0955fb4ca78dde", 91 | "sha256:e189e728e50cfb15746abc6b3178ca0e2bebbb7a8d3d98fbc9e24bc1a4c65564" 92 | ], 93 | "index": "pypi", 94 | "markers": "python_version >= '3.9'", 95 | "version": "==0.4.1" 96 | }, 97 | "mdformat-tables": { 98 | "hashes": [ 99 | "sha256:94cd86126141b2adc3b04c08d1441eb1272b36c39146bab078249a41c7240a9a", 100 | "sha256:a57db1ac17c4a125da794ef45539904bb8a9592e80557d525e1f169c96daa2c8" 101 | ], 102 | "markers": "python_full_version >= '3.7.0'", 103 | "version": "==1.0.0" 104 | }, 105 | "mdit-py-plugins": { 106 | "hashes": [ 107 | "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636", 108 | "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5" 109 | ], 110 | "markers": "python_version >= '3.8'", 111 | "version": "==0.4.2" 112 | }, 113 | "mdurl": { 114 | "hashes": [ 115 | "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", 116 | "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" 117 | ], 118 | "markers": "python_version >= '3.7'", 119 | "version": "==0.1.2" 120 | }, 121 | "mypy-extensions": { 122 | "hashes": [ 123 | "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", 124 | "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558" 125 | ], 126 | "markers": "python_version >= '3.8'", 127 | "version": "==1.1.0" 128 | }, 129 | "packaging": { 130 | "hashes": [ 131 | "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", 132 | "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" 133 | ], 134 | "markers": "python_version >= '3.8'", 135 | "version": "==25.0" 136 | }, 137 | "pathspec": { 138 | "hashes": [ 139 | "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", 140 | "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" 141 | ], 142 | "markers": "python_version >= '3.8'", 143 | "version": "==0.12.1" 144 | }, 145 | "platformdirs": { 146 | "hashes": [ 147 | "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", 148 | "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4" 149 | ], 150 | "markers": "python_version >= '3.9'", 151 | "version": "==4.3.8" 152 | }, 153 | "ruff": { 154 | "hashes": [ 155 | "sha256:1adcb9a18802268aaa891ffb67b1c94cd70578f126637118e8099b8e4adcf112", 156 | "sha256:1b5ab797fcc09121ed82e9b12b6f27e34859e4227080a42d090881be888755d4", 157 | "sha256:6224076c344a7694c6fbbb70d4f2a7b730f6d47d2a9dc1e7f9d9bb583faf390b", 158 | "sha256:64ac6f885e3ecb2fdbb71de2701d4e34526651f1e8503af8fb30d4915a3fe345", 159 | "sha256:6c51f136c0364ab1b774767aa8b86331bd8e9d414e2d107db7a2189f35ea1f7b", 160 | "sha256:748b4bb245f11e91a04a4ff0f96e386711df0a30412b9fe0c74d5bdc0e4a531f", 161 | "sha256:7774173cc7c1980e6bf67569ebb7085989a78a103922fb83ef3dfe230cd0687d", 162 | "sha256:7885d9a5e4c77b24e8c88aba8c80be9255fa22ab326019dac2356cff42089fc6", 163 | "sha256:882821fcdf7ae8db7a951df1903d9cb032bbe838852e5fc3c2b6c3ab54e39875", 164 | "sha256:9263f9e5aa4ff1dec765e99810f1cc53f0c868c5329b69f13845f699fe74f639", 165 | "sha256:9924e5ae54125ed8958a4f7de320dab7380f6e9fa3195e3dc3b137c6842a0092", 166 | "sha256:99c28505ecbaeb6594701a74e395b187ee083ee26478c1a795d35084d53ebd81", 167 | "sha256:a97c9babe1d4081037a90289986925726b802d180cca784ac8da2bbbc335f709", 168 | "sha256:c8a93276393d91e952f790148eb226658dd275cddfde96c6ca304873f11d2ae4", 169 | "sha256:d6e333dbe2e6ae84cdedefa943dfd6434753ad321764fd937eef9d6b62022bcd", 170 | "sha256:d8c4ddcbe8a19f59f57fd814b8b117d4fcea9bee7c0492e6cf5fdc22cfa563c8", 171 | "sha256:dcec2d50756463d9df075a26a85a6affbc1b0148873da3997286caf1ce03cae1", 172 | "sha256:e231ff3132c1119ece836487a02785f099a43992b95c2f62847d29bace3c75ac" 173 | ], 174 | "index": "pypi", 175 | "markers": "python_version >= '3.7'", 176 | "version": "==0.11.11" 177 | }, 178 | "tomli": { 179 | "hashes": [ 180 | "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", 181 | "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", 182 | "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", 183 | "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", 184 | "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", 185 | "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", 186 | "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", 187 | "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", 188 | "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", 189 | "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", 190 | "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", 191 | "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", 192 | "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", 193 | "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", 194 | "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", 195 | "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", 196 | "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", 197 | "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", 198 | "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", 199 | "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", 200 | "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", 201 | "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", 202 | "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", 203 | "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", 204 | "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", 205 | "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", 206 | "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", 207 | "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", 208 | "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", 209 | "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", 210 | "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", 211 | "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7" 212 | ], 213 | "markers": "python_version >= '3.8'", 214 | "version": "==2.2.1" 215 | }, 216 | "typing-extensions": { 217 | "hashes": [ 218 | "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", 219 | "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef" 220 | ], 221 | "markers": "python_version >= '3.8'", 222 | "version": "==4.13.2" 223 | }, 224 | "wcwidth": { 225 | "hashes": [ 226 | "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", 227 | "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5" 228 | ], 229 | "version": "==0.2.13" 230 | }, 231 | "zipp": { 232 | "hashes": [ 233 | "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4", 234 | "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931" 235 | ], 236 | "markers": "python_version >= '3.9'", 237 | "version": "==3.21.0" 238 | } 239 | }, 240 | "develop": {} 241 | } 242 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vulkan Layer for Rust 2 | 3 | [![postsubmit](https://github.com/google/vk-layer-for-rust/actions/workflows/postsubmit.yml/badge.svg)](https://github.com/google/vk-layer-for-rust/actions/workflows/postsubmit.yml) 4 | [![postsubmit miri](https://github.com/google/vk-layer-for-rust/actions/workflows/postsubmit-miri.yml/badge.svg?branch=main)](https://github.com/google/vk-layer-for-rust/actions/workflows/postsubmit-miri.yml) 5 | [![Linux coverage](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fgoogle%2Fvk-layer-for-rust%2Fstatic_resource%2Fcoverage-Linux%2Fcoverage_badge.json)](https://google.github.io/vk-layer-for-rust/coverage-Linux/llvm-cov/html/index.html) 6 | [![Windows coverage](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fgoogle%2Fvk-layer-for-rust%2Fstatic_resource%2Fcoverage-Windows%2Fcoverage_badge.json)](https://google.github.io/vk-layer-for-rust/coverage-Windows/llvm-cov/html/index.html) 7 | [![Linux doc](https://img.shields.io/badge/doc-Linux-blue)](https://google.github.io/vk-layer-for-rust/doc-Linux/vulkan_layer/index.html) 8 | [![Windows doc](https://img.shields.io/badge/doc-Windows-blue)](https://google.github.io/vk-layer-for-rust/doc-Windows/vulkan_layer/index.html) 9 | 10 | This project provides a way to use safe Rust to write a 11 | [Vulkan layer](https://github.com/KhronosGroup/Vulkan-Loader/blob/121c1f42025a82dca7922a503ca77df51c37b394/docs/LoaderInterfaceArchitecture.md#layers). 12 | 13 | ## Contributing 14 | 15 | See [`CONTRIBUTING.md`](CONTRIBUTING.md) for details. 16 | 17 | ## License 18 | 19 | Apache 2.0; see [`LICENSE`](LICENSE) for details. 20 | 21 | ## Disclaimer 22 | 23 | This project is not an official Google project. It is not supported by Google and Google 24 | specifically disclaims all warranties as to its quality, merchantability, or fitness for a 25 | particular purpose. 26 | 27 | ## Build 28 | 29 | ### cross-compile from Windows to Android 30 | 31 | 1. Install the Rust Android toolchain 32 | 33 | ```bash 34 | rustup target add x86_64-linux-android 35 | ``` 36 | 37 | 1. Set up the linker to use. Add the following lines to the 38 | [`config.toml` file](https://doc.rust-lang.org/cargo/reference/config.html#hierarchical-structure). 39 | I will just use `%CARGO_HOME%/config.toml`. 40 | 41 | ```plaintext 42 | [target.x86_64-linux-android] 43 | linker = "%NDK_HOME%\\toolchains\\llvm\\prebuilt\\windows-x86_64\\bin\\x86_64-linux-android33-clang" 44 | ``` 45 | 46 | Note that `%NDK_HOME%` must be expanded to an explicit absolute path, which is usually 47 | `%LOCALAPPDATA%/Android/Sdk/ndk/`. 48 | 49 | 1. If the NDK version is greater than 22, follow 50 | [this workaround](https://stackoverflow.com/a/74041320). 51 | 52 | 1. `cargo build --target x86_64-linux-android`. 53 | 54 | ### Windows natively 55 | 56 | ```bash 57 | cargo build 58 | ``` 59 | 60 | ### Soong in an Android tree 61 | 62 | TODO 63 | 64 | ## TODO 65 | 66 | - \[x\] Set up `Android.bp` to build in an Android tree. 67 | - \[ \] Upgrade ash in aosp, and remove vulkano, so that we can build from aosp/master. 68 | - \[x\] Auto-generate the binding from 69 | [`vk_layer.h`](https://github.com/KhronosGroup/Vulkan-Headers/blob/9e61870ecbd32514113b467e0a0c46f60ed222c7/include/vulkan/vk_layer.h). 70 | - \[x\] Auto-generate the `Layer` trait and the interception code from `vk.xml`. 71 | - \[x\] Use an attribute macro to track which function is implemented in the `Layer` trait, and don't 72 | inject all other functions for performance. 73 | - \[x\] Make global instance trivially destructible after all instances are destroyed, so the layer is 74 | robust against the situation where the dynamic library is loaded and unloaded for several times. 75 | - \[x\] Use procedure macro to generate the export functions in `lib.rs` file for the layer user. 76 | - \[ \] Use the cargo-make workflow to generate the layer json files for examples. 77 | - \[ \] Support the latest layer interface version. Currently 2 is the latest version. e.g. correctly 78 | handle the `vk_layerGetPhysicalDeviceProcAddr` case. 79 | - \[ \] Allow intercepting 80 | [pre-instance functions](https://github.com/KhronosGroup/Vulkan-Loader/blob/0c63db1aeda6916690b863688fa6cdf2ac1f790b/docs/LoaderLayerInterface.md#pre-instance-functions). 81 | - \[ \] Add docstring to generated `layer_trait.rs` file. 82 | - \[ \] Testing 83 | - \[ \] e2e test: the test boundary is the Vulkan layer ABI. The Vulkan loader and the mock ICD will 84 | be used for testing. Write a `cdylib` crate named `tests`, with a layer that is allowed to 85 | customize the behavior through a function exported as a dynamic library symbol. We run different 86 | tests in different processes. For different tests, we customize the layer differently, and 87 | asserts either inside the customization point or after it returns, e.g. to test initialization 88 | and destruction on DLL loading and unloading time, we can customize the ctor and the drop 89 | implementation for the layer, then load and unload the Vulkan library then verify if the ctor is 90 | called the same time as the drop. We also need to create a `e2e` task to build the DLL, generate 91 | the json, set the environment variables, and spawn tests in different processes(cargo-nextest 92 | can be used here since it runs tests in their own process). 93 | - \[x\] `vulkan-layer` level integration test 94 | - \[ \] catch unwind at the FFI boundary to allow the library to be compiled with `panic="unwind"`. 95 | - \[ \] Improve Miri test 96 | - Check why fp comparison in miri fails in test_should_move_layer_device_link_forward, 97 | test_should_move_layer_device_link_forward, and 98 | test_should_return_fp_for_get_instance_proc_addr. 99 | -------------------------------------------------------------------------------- /examples/hello-world/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | [package] 16 | edition = "2021" 17 | name = "vk-layer-example-hello-world" 18 | version = "0.1.0" 19 | 20 | [lib] 21 | crate-type = ["cdylib"] 22 | name = "VkLayer_vendor_rust_example_hello_world" 23 | 24 | [profile.dev] 25 | panic = "abort" 26 | 27 | [profile.release] 28 | panic = "abort" 29 | 30 | [dependencies] 31 | ash = "0.37.3" 32 | once_cell = "1.17.1" 33 | vulkan-layer = { path = "../../vulkan-layer" } 34 | -------------------------------------------------------------------------------- /examples/hello-world/src/impl.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use ash::{self, vk}; 16 | use once_cell::sync::Lazy; 17 | use std::sync::Arc; 18 | use vulkan_layer::{ 19 | declare_introspection_queries, Global, Layer, LayerManifest, StubDeviceInfo, StubGlobalHooks, 20 | StubInstanceInfo, 21 | }; 22 | 23 | #[derive(Default)] 24 | struct MyLayer(StubGlobalHooks); 25 | 26 | impl Layer for MyLayer { 27 | type GlobalHooksInfo = StubGlobalHooks; 28 | type InstanceInfo = StubInstanceInfo; 29 | type DeviceInfo = StubDeviceInfo; 30 | type InstanceInfoContainer = StubInstanceInfo; 31 | type DeviceInfoContainer = StubDeviceInfo; 32 | 33 | fn global_instance() -> impl std::ops::Deref> + 'static { 34 | static GLOBAL: Lazy> = Lazy::new(Default::default); 35 | &*GLOBAL 36 | } 37 | 38 | fn manifest() -> LayerManifest { 39 | let mut manifest = LayerManifest::default(); 40 | // Must match with the binary name on Android. 41 | manifest.name = "VK_LAYER_VENDOR_rust_example_hello_world"; 42 | manifest.spec_version = vk::API_VERSION_1_1; 43 | manifest.description = "Rust test layer hello world"; 44 | manifest 45 | } 46 | 47 | fn global_hooks_info(&self) -> &Self::GlobalHooksInfo { 48 | &self.0 49 | } 50 | 51 | fn create_instance_info( 52 | &self, 53 | _: &vk::InstanceCreateInfo, 54 | _: Option<&vk::AllocationCallbacks>, 55 | _: Arc, 56 | _next_get_instance_proc_addr: vk::PFN_vkGetInstanceProcAddr, 57 | ) -> Self::InstanceInfoContainer { 58 | Default::default() 59 | } 60 | 61 | fn create_device_info( 62 | &self, 63 | _: vk::PhysicalDevice, 64 | _: &vk::DeviceCreateInfo, 65 | _: Option<&vk::AllocationCallbacks>, 66 | _: Arc, 67 | _next_get_device_proc_addr: vk::PFN_vkGetDeviceProcAddr, 68 | ) -> Self::DeviceInfoContainer { 69 | println!("Hello from the Rust Vulkan layer!"); 70 | Default::default() 71 | } 72 | } 73 | 74 | declare_introspection_queries!(Global::); 75 | -------------------------------------------------------------------------------- /examples/hello-world/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Disable the non_snake_case lint so that we can have non-snake-case binary name which is required 16 | // Android. See https://github.com/rust-lang/rust/issues/45127 for details on Rust lint. 17 | #![allow(non_snake_case)] 18 | 19 | // All the implementation is in a separate module with the non_snake_case lint reenabled. 20 | #[warn(non_snake_case)] 21 | mod r#impl; 22 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | [tool.black] 16 | extend-exclude = '^/third_party/.*' 17 | line-length = 100 18 | preview = true 19 | target-version = ['py39'] 20 | 21 | [tool.ruff] 22 | exclude = [ 23 | ".bzr", 24 | ".direnv", 25 | ".eggs", 26 | ".git", 27 | ".git-rewrite", 28 | ".hg", 29 | ".mypy_cache", 30 | ".nox", 31 | ".pants.d", 32 | ".pytype", 33 | ".ruff_cache", 34 | ".svn", 35 | ".tox", 36 | ".venv", 37 | "__pypackages__", 38 | "_build", 39 | "buck-out", 40 | "build", 41 | "dist", 42 | "node_modules", 43 | "venv", 44 | "third_party", 45 | ] 46 | line-length = 100 47 | target-version = "py39" 48 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | comment_width = 100 16 | doc_comment_code_block_width = 100 17 | format_code_in_doc_comments = true 18 | format_strings = true 19 | max_width = 100 20 | unstable_features = true 21 | wrap_comments = true 22 | -------------------------------------------------------------------------------- /scripts/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | [package] 16 | edition = "2021" 17 | name = "vulkan-layer-scripts" 18 | version = "0.1.0" 19 | 20 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 21 | 22 | [dependencies] 23 | anyhow = "1.0.86" 24 | bindgen = "0.69.4" 25 | clap = { version = "4.5.8", features = ["derive"] } 26 | env_logger = "0.11.3" 27 | glob = "0.3.1" 28 | ignore = "0.4.22" 29 | imara-diff = "0.1.6" 30 | itertools = "0.13.0" 31 | log = "0.4.22" 32 | serde = { version = "1.0.203", features = ["derive"] } 33 | serde_json = "1.0.120" 34 | shlex = "1.3.0" 35 | tempfile = "3.10.1" 36 | walkdir = "2.5.0" 37 | 38 | [lib] 39 | path = "lib.rs" 40 | 41 | [[bin]] 42 | name = "ci" 43 | path = "ci.rs" 44 | 45 | [[bin]] 46 | name = "codegen" 47 | path = "codegen.rs" 48 | 49 | [[bin]] 50 | name = "list-files" 51 | path = "list_files/main.rs" 52 | -------------------------------------------------------------------------------- /scripts/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /scripts/ci.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::{fs::File, io::BufReader, path::PathBuf}; 16 | 17 | use clap::Parser; 18 | use log::error; 19 | use serde::{Deserialize, Serialize}; 20 | 21 | #[derive(Parser)] 22 | struct Cli { 23 | /// The path to the output json file. 24 | #[arg(long)] 25 | output_path: PathBuf, 26 | 27 | /// The left text of the coverage badge. 28 | #[arg(long)] 29 | label: String, 30 | 31 | /// The json field name and the path to the json coverage report separated by colon. 32 | coverage_report: PathBuf, 33 | } 34 | 35 | #[derive(Deserialize)] 36 | struct CoverageStats { 37 | percent: f64, 38 | } 39 | 40 | #[derive(Deserialize)] 41 | struct CoverageReportDataTotals { 42 | lines: CoverageStats, 43 | } 44 | 45 | #[derive(Deserialize)] 46 | struct CoverageReportData { 47 | totals: CoverageReportDataTotals, 48 | } 49 | 50 | #[derive(Deserialize)] 51 | struct CoverageReport { 52 | data: Vec, 53 | } 54 | 55 | #[derive(Serialize)] 56 | #[serde(rename_all = "camelCase")] 57 | struct ShieldBadge { 58 | schema_version: u32, 59 | label: String, 60 | message: String, 61 | color: String, 62 | } 63 | 64 | fn create_shield_badge(label: String, coverage_stats: &CoverageStats) -> ShieldBadge { 65 | let percent = coverage_stats.percent; 66 | let color = if (0.0..75.0).contains(&percent) { 67 | "#e05d44" 68 | } else if (75.0..90.0).contains(&percent) { 69 | "#dfb317" 70 | } else if (90.0..95.0).contains(&percent) { 71 | "#a3c51c" 72 | } else if (95.0..100.0).contains(&percent) { 73 | "#4c1" 74 | } else { 75 | "#9f9f9f" 76 | }; 77 | let message = if (0.0..=100.0).contains(&percent) { 78 | format!("{:.1}%", percent) 79 | } else { 80 | error!("Invalid percentage: {}", percent); 81 | "unknown".to_owned() 82 | }; 83 | ShieldBadge { 84 | schema_version: 1, 85 | label, 86 | message, 87 | color: color.to_owned(), 88 | } 89 | } 90 | 91 | fn main() { 92 | env_logger::Builder::new() 93 | .filter_level(log::LevelFilter::Trace) 94 | .init(); 95 | let cli = Cli::parse(); 96 | assert!( 97 | cli.coverage_report.exists(), 98 | "{} doesn't exist.", 99 | cli.coverage_report.display() 100 | ); 101 | let file = File::open(&cli.coverage_report).unwrap_or_else(|e| { 102 | panic!( 103 | "Failed to open {} to read: {:?}", 104 | cli.coverage_report.display(), 105 | e 106 | ) 107 | }); 108 | let reader = BufReader::new(file); 109 | let report: CoverageReport = serde_json::from_reader(reader) 110 | .unwrap_or_else(|e| panic!("Failed to parse the report: {:?}", e)); 111 | assert!( 112 | !report.data.is_empty(), 113 | "Unexpected data field length: {}", 114 | report.data.len() 115 | ); 116 | let shield_badge = create_shield_badge(cli.label, &report.data[0].totals.lines); 117 | let mut out_file = File::create(&cli.output_path).unwrap_or_else(|e| { 118 | panic!( 119 | "Failed to open the output file {}: {:?}", 120 | cli.output_path.display(), 121 | e 122 | ) 123 | }); 124 | serde_json::to_writer(&mut out_file, &shield_badge) 125 | .unwrap_or_else(|e| panic!("Failed to write the shield badge: {:?}", e)); 126 | } 127 | -------------------------------------------------------------------------------- /scripts/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | pub mod list_files; 16 | -------------------------------------------------------------------------------- /scripts/list_files/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use itertools::Itertools; 16 | use log::error; 17 | use std::sync::{Arc, Mutex}; 18 | 19 | use anyhow::{bail, Context}; 20 | use vulkan_layer_scripts::list_files::{parse_args, TaskBuilder}; 21 | 22 | fn main() -> anyhow::Result<()> { 23 | env_logger::Builder::new() 24 | .filter_level(log::LevelFilter::Trace) 25 | .init(); 26 | 27 | let cli = match parse_args(std::env::args_os()) { 28 | Ok(cli) => cli, 29 | Err(e) => e.exit(), 30 | }; 31 | 32 | let paths = cli.get_paths().context("retrieve paths from CLI")?; 33 | let task_builder = TaskBuilder::new(cli.command.clone()); 34 | 35 | let errors = std::thread::scope(move |s| { 36 | let errors: Arc>> = Arc::default(); 37 | for paths in &paths.into_iter().chunks(10) { 38 | if !errors.lock().unwrap().is_empty() { 39 | break; 40 | } 41 | let paths = paths.collect::>(); 42 | let task = task_builder.build_task(paths.iter().map(|path| path.as_ref())); 43 | s.spawn({ 44 | let errors = errors.clone(); 45 | move || { 46 | if let Err(e) = task.run(&mut std::io::stdout(), &mut std::io::stderr()) { 47 | errors.lock().unwrap().push(e); 48 | } 49 | } 50 | }); 51 | } 52 | errors.clone() 53 | }); 54 | 55 | let errors = errors.lock().unwrap(); 56 | for error in errors.iter() { 57 | error!("Failed to complete task: {:?}", error); 58 | } 59 | if !errors.is_empty() { 60 | bail!("Failed to complete all tasks"); 61 | } 62 | Ok(()) 63 | } 64 | -------------------------------------------------------------------------------- /scripts/list_files/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::{ 16 | ffi::OsString, 17 | path::{Path, PathBuf}, 18 | }; 19 | 20 | use anyhow::{anyhow, ensure, Context}; 21 | use clap::{Error as ClapError, Parser}; 22 | use glob::Pattern; 23 | use ignore::gitignore::Gitignore; 24 | use log::error; 25 | use std::borrow::Borrow; 26 | use std::io; 27 | use walkdir::WalkDir; 28 | 29 | const ABOUT: &str = "List files and forward the canonical path as the arguments. If the command \ 30 | failed, the exit code will be non-zero. If a file can't be accessed, the \ 31 | file will be skipped instead of triggering an error."; 32 | 33 | #[derive(Parser, Debug)] 34 | #[command(about = ABOUT)] 35 | pub struct Cli { 36 | /// Globs to include in search. Paths that match any of include patterns will be included. If 37 | /// no include pattern is provided, "*" is used. 38 | #[arg(long)] 39 | pub include: Vec, 40 | 41 | /// Globs to exclude from search. Paths that match any of exclude patterns will not be 42 | /// included. 43 | #[arg(long)] 44 | pub ignore: Vec, 45 | 46 | /// Respect the .gitignore file. Will find the .gitignore file under the root directory. 47 | #[arg(long)] 48 | pub use_gitignore: bool, 49 | 50 | /// Search root. 51 | pub root: OsString, 52 | 53 | /// Command to run on the matching files. 54 | #[arg(last = true)] 55 | pub command: Vec, 56 | } 57 | 58 | impl Cli { 59 | pub fn get_paths(&self) -> anyhow::Result> { 60 | fn parse_pattern(pattern: &String) -> anyhow::Result { 61 | Pattern::new(pattern).map_err(|e| anyhow!("Failed to parse pattern {}: {}", pattern, e)) 62 | } 63 | let include_patterns = self 64 | .include 65 | .iter() 66 | .map(parse_pattern) 67 | .collect::>>() 68 | .context("parse include patterns")?; 69 | let ignore_patterns = { 70 | let mut ignore_patterns = self 71 | .ignore 72 | .iter() 73 | .map(parse_pattern) 74 | .collect::>>() 75 | .context("parse ignore patterns")?; 76 | if self.use_gitignore { 77 | ignore_patterns 78 | .push(Pattern::new("**/.git").expect("parsing .git pattern should succeed")); 79 | } 80 | ignore_patterns 81 | }; 82 | 83 | let gitignore = if self.use_gitignore { 84 | let mut gitignore_path = PathBuf::from(&self.root); 85 | gitignore_path.push(".gitignore"); 86 | let (gitignore, error) = Gitignore::new(&gitignore_path); 87 | if let Some(error) = error { 88 | error!( 89 | "Failed to create a new gitignore matcher from {}: {}", 90 | gitignore_path.display(), 91 | error 92 | ); 93 | None 94 | } else { 95 | Some(gitignore) 96 | } 97 | } else { 98 | None 99 | }; 100 | let walk_dir = WalkDir::new(&self.root); 101 | let res = FileIteratorBuilder { 102 | include_patterns, 103 | ignore_patterns, 104 | gitignore, 105 | walk_dir, 106 | } 107 | .build() 108 | .filter_map(|path| match path { 109 | Ok(path) => Some(path), 110 | Err(e) => { 111 | error!("Failed to visit one path: {:?}", e); 112 | None 113 | } 114 | }); 115 | Ok(res) 116 | } 117 | } 118 | 119 | pub fn parse_args(args: impl IntoIterator) -> Result 120 | where 121 | T: Into + Clone, 122 | { 123 | let mut cli = Cli::try_parse_from(args)?; 124 | if cli.include.is_empty() { 125 | cli.include.push("*".to_string()); 126 | } 127 | Ok(cli) 128 | } 129 | 130 | struct FileIteratorBuilder { 131 | pub include_patterns: Vec, 132 | pub ignore_patterns: Vec, 133 | pub gitignore: Option, 134 | pub walk_dir: WalkDir, 135 | } 136 | 137 | impl Default for FileIteratorBuilder { 138 | fn default() -> Self { 139 | Self { 140 | include_patterns: vec![], 141 | ignore_patterns: vec![], 142 | gitignore: None, 143 | walk_dir: WalkDir::new("."), 144 | } 145 | } 146 | } 147 | 148 | impl FileIteratorBuilder { 149 | fn build(self) -> FileIterator { 150 | let Self { 151 | include_patterns, 152 | ignore_patterns, 153 | gitignore, 154 | walk_dir, 155 | } = self; 156 | FileIterator { 157 | include_patterns, 158 | ignore_patterns, 159 | gitignore, 160 | current: walk_dir.into_iter(), 161 | } 162 | } 163 | } 164 | 165 | struct FileIterator { 166 | include_patterns: Vec, 167 | ignore_patterns: Vec, 168 | gitignore: Option, 169 | current: walkdir::IntoIter, 170 | } 171 | 172 | impl Iterator for FileIterator { 173 | type Item = anyhow::Result; 174 | 175 | fn next(&mut self) -> Option { 176 | while let Some(res) = self.current.next() { 177 | let entry = match res { 178 | Ok(entry) => entry, 179 | Err(e) => return Some(Err(anyhow!("Failed to visit the next entry: {:?}", e))), 180 | }; 181 | let mut should_ignore = self 182 | .ignore_patterns 183 | .iter() 184 | .any(|pat| pat.matches_path(entry.path())); 185 | let file_type = entry.file_type(); 186 | if let Some(gitignore) = &self.gitignore { 187 | should_ignore = should_ignore 188 | || gitignore 189 | .matched(entry.path(), file_type.is_dir()) 190 | .is_ignore(); 191 | } 192 | if should_ignore && file_type.is_dir() { 193 | self.current.skip_current_dir(); 194 | continue; 195 | } 196 | if !file_type.is_file() { 197 | continue; 198 | } 199 | let mut should_include = !should_ignore; 200 | should_include = should_include 201 | && self 202 | .include_patterns 203 | .iter() 204 | .any(|pat| pat.matches_path(entry.path())); 205 | if should_include { 206 | return Some(Ok(entry.into_path())); 207 | } 208 | } 209 | None 210 | } 211 | } 212 | 213 | pub struct TaskBuilder { 214 | command: Vec, 215 | } 216 | 217 | impl TaskBuilder { 218 | pub fn new(command: Vec) -> Self { 219 | Self { command } 220 | } 221 | 222 | pub fn build_task<'a>(&self, paths: impl IntoIterator) -> Task { 223 | if self.command.is_empty() { 224 | let mut to_print = OsString::new(); 225 | let mut first = true; 226 | for path in paths { 227 | if first { 228 | first = false; 229 | } else { 230 | to_print.push(" "); 231 | } 232 | to_print.push(path); 233 | } 234 | Task::Print(to_print) 235 | } else { 236 | let mut command = std::process::Command::new(&self.command[0]); 237 | for arg in self.command.iter().skip(1) { 238 | command.arg(arg); 239 | } 240 | for path in paths { 241 | command.arg(path); 242 | } 243 | Task::SpawnProcess(command) 244 | } 245 | } 246 | } 247 | 248 | pub enum Task { 249 | Print(OsString), 250 | SpawnProcess(std::process::Command), 251 | } 252 | 253 | impl Task { 254 | pub fn run( 255 | self, 256 | stdout_buffer: &mut impl io::Write, 257 | stderr_buffer: &mut impl io::Write, 258 | ) -> anyhow::Result<()> { 259 | match self { 260 | Self::Print(to_print) => { 261 | let to_print = to_print.to_string_lossy(); 262 | let to_print: &str = to_print.borrow(); 263 | writeln!(stdout_buffer, "{}", to_print).context("print matched paths") 264 | } 265 | Self::SpawnProcess(mut command) => { 266 | let command_str = { 267 | let mut command_strs = 268 | vec![command.get_program().to_string_lossy().to_string()]; 269 | for arg in command.get_args() { 270 | command_strs.push(arg.to_string_lossy().to_string()); 271 | } 272 | let command = shlex::try_join(command_strs.iter().map(String::as_ref)) 273 | .expect("should not fail to join"); 274 | command 275 | }; 276 | let output = command 277 | .output() 278 | .with_context(|| format!("spawn and wait for the command {}", &command_str))?; 279 | ensure!(output.status.success(), { 280 | match &output.status.code() { 281 | Some(exit_code) => format!( 282 | "The process {} failed with exit code {}.", 283 | command_str, exit_code 284 | ), 285 | None => format!("The process {} failed without an exit code.", command_str), 286 | } 287 | }); 288 | if let Err(e) = stdout_buffer.write_all(&output.stdout) { 289 | error!( 290 | "Failed to write the stdout for process {}: {:?}", 291 | command_str, e 292 | ); 293 | } 294 | if let Err(e) = stderr_buffer.write_all(&output.stderr) { 295 | error!( 296 | "Failed to write the stderr for process {}: {:?}", 297 | command_str, e 298 | ); 299 | } 300 | Ok(()) 301 | } 302 | } 303 | } 304 | } 305 | 306 | #[cfg(test)] 307 | mod tests { 308 | use super::*; 309 | 310 | #[test] 311 | fn test_parse_args() { 312 | let args = [ 313 | "progname", 314 | "--include=*.yaml", 315 | "--include", 316 | "*.yml", 317 | "--ignore=.git", 318 | "--ignore=__pycache__", 319 | "tmp/subdir", 320 | "--", 321 | "ls", 322 | "-lah", 323 | ]; 324 | let cli = parse_args(args).expect("should parse successfully"); 325 | assert_eq!(cli.include, vec!["*.yaml", "*.yml"]); 326 | assert_eq!(cli.ignore, vec![".git", "__pycache__"]); 327 | assert_eq!(cli.command, vec!["ls", "-lah"]); 328 | assert_eq!(cli.root, "tmp/subdir"); 329 | } 330 | 331 | #[test] 332 | fn test_parse_args_missing_root() { 333 | let res = parse_args(["progname"]); 334 | assert!(res.is_err()); 335 | } 336 | 337 | #[test] 338 | fn test_parse_args_missing_include() { 339 | let cli = parse_args(["progname", "."]).expect("should parse successfully"); 340 | assert_eq!(cli.include, vec!["*"]); 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /scripts/vulkan_layer_genvk/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import sys 16 | from pathlib import Path 17 | 18 | sys.path.insert( 19 | 0, str(Path(__file__).absolute().parents[2] / "third_party" / "Vulkan-Headers" / "registry") 20 | ) 21 | -------------------------------------------------------------------------------- /scripts/vulkan_layer_genvk/__main__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import argparse 16 | import logging 17 | from pathlib import Path 18 | import pdb 19 | import re 20 | import sys 21 | import xml.etree.ElementTree as etree 22 | 23 | from generator import OutputGenerator, GeneratorOptions 24 | from .layer_trait_generator import LayerTraitGenerator 25 | from .global_simple_intercept_generator import GlobalSimpleInterceptGenerator 26 | from reg import Registry 27 | from vkconventions import VulkanConventions 28 | from spec_tools.conventions import ConventionsBase 29 | 30 | 31 | def make_re_string(strings, default=None, strings_are_regex=False): 32 | """Turn a list of strings into a regexp string matching exactly those strings.""" 33 | if strings or default is None: 34 | if not strings_are_regex: 35 | strings = (re.escape(s) for s in strings) 36 | return "^(" + "|".join(strings) + ")$" 37 | return default 38 | 39 | 40 | def make_gen_opts( 41 | conventions: ConventionsBase, 42 | directory: str, 43 | feature_pat: str, 44 | add_extension_pat: str, 45 | remove_extension_pat: str, 46 | emit_extension_pat: str, 47 | ) -> dict[Path, tuple[OutputGenerator, GeneratorOptions]]: 48 | return { 49 | Path("layer_trait/generated.rs"): ( 50 | LayerTraitGenerator, 51 | GeneratorOptions( 52 | conventions=conventions, 53 | filename="layer_trait/generated.rs", 54 | directory=directory, 55 | genpath=None, 56 | apiname="vulkan", 57 | profile=None, 58 | versions=feature_pat, 59 | emitversions=feature_pat, 60 | defaultExtensions="vulkan", 61 | addExtensions=add_extension_pat, 62 | removeExtensions=remove_extension_pat, 63 | emitExtensions=emit_extension_pat, 64 | ), 65 | ), 66 | Path("global_simple_intercept/generated.rs"): ( 67 | GlobalSimpleInterceptGenerator, 68 | GeneratorOptions( 69 | conventions=conventions, 70 | filename="global_simple_intercept/generated.rs", 71 | directory=directory, 72 | genpath=None, 73 | apiname="vulkan", 74 | profile=None, 75 | versions=feature_pat, 76 | emitversions=feature_pat, 77 | defaultExtensions="vulkan", 78 | addExtensions=add_extension_pat, 79 | removeExtensions=remove_extension_pat, 80 | emitExtensions=emit_extension_pat, 81 | ), 82 | ), 83 | } 84 | 85 | 86 | def main(): 87 | logging.basicConfig( 88 | format=( 89 | "[%(asctime)s %(levelname)s %(module)s] %(funcName)s(%(filename)s:%(lineno)d): " 90 | "%(message)s" 91 | ), 92 | level=logging.INFO, 93 | ) 94 | 95 | parser = argparse.ArgumentParser(prog="python -m vulkan_layer_genvk") 96 | parser.add_argument( 97 | "-registry", 98 | action="store", 99 | required=True, 100 | help="Use specified registry file instead of vk.xml", 101 | ) 102 | parser.add_argument("target", metavar="target", help="Specify target") 103 | parser.add_argument("-dump", action="store_true", help="Enable dump to stderr") 104 | parser.add_argument("-debug", action="store_true", help="Enable debugging") 105 | parser.add_argument( 106 | "-quiet", 107 | action="store_true", 108 | default=True, 109 | help="Suppress script output during normal execution.", 110 | ) 111 | parser.add_argument( 112 | "-feature", 113 | action="append", 114 | default=[], 115 | help="Specify a core API feature name or names to add to targets", 116 | ) 117 | parser.add_argument( 118 | "-o", 119 | action="store", 120 | dest="directory", 121 | default=".", 122 | help="Create target and related files in specified directory", 123 | ) 124 | parser.add_argument( 125 | "-extension", 126 | action="append", 127 | default=[], 128 | help="Specify an extension or extensions to add to targets", 129 | ) 130 | parser.add_argument( 131 | "-remove-extensions", 132 | action="append", 133 | default=[], 134 | help="Specify an extension or extensions to remove from targets", 135 | ) 136 | parser.add_argument( 137 | "-emit-extensions", 138 | action="append", 139 | default=[], 140 | help="Specify an extension or extensions to emit in targets", 141 | ) 142 | args = parser.parse_args() 143 | 144 | args.feature = [name for arg in args.feature for name in arg.split()] 145 | args.extension = [name for arg in args.extension for name in arg.split()] 146 | 147 | all_features = all_extensions = r".*" 148 | 149 | gen_opts = make_gen_opts( 150 | conventions=VulkanConventions(), 151 | directory=args.directory, 152 | feature_pat=make_re_string(args.feature, all_features), 153 | add_extension_pat=make_re_string(args.extension, None), 154 | remove_extension_pat=make_re_string(args.remove_extensions, None), 155 | emit_extension_pat=make_re_string(args.emit_extensions, all_extensions), 156 | ) 157 | gen_opt = gen_opts.get(Path(args.target)) 158 | 159 | if gen_opt is None: 160 | logging.fatal("target %s not found", args.target) 161 | return 162 | (generator_creator, generator_option) = gen_opt 163 | generator = generator_creator(err_file=sys.stderr, warn_file=sys.stderr, diag_file=None) 164 | 165 | reg = Registry(generator, generator_option) 166 | tree = etree.parse(args.registry) 167 | 168 | # Allow the generation of the VK_ANDROID_native_buffer extension. 169 | for android_ext_elem in tree.findall( 170 | ".//extension[@name='VK_ANDROID_native_buffer'][@supported]" 171 | ): 172 | android_ext_elem.set("supported", "vulkan") 173 | 174 | # Load the XML tree into the registry object 175 | reg.loadElementTree(tree) 176 | 177 | if args.dump: 178 | logging.info("* Dumping registry to regdump.txt") 179 | reg.dumpReg(filehandle=open("regdump.txt", "w", encoding="utf-8")) 180 | 181 | # Finally, use the output generator to create the requested target 182 | if args.debug: 183 | pdb.run("reg.apiGen()") 184 | else: 185 | reg.apiGen() 186 | 187 | if not args.quiet: 188 | logging.info("* Generated ", generator_option.filename) 189 | 190 | 191 | if __name__ == "__main__": 192 | main() 193 | -------------------------------------------------------------------------------- /scripts/vulkan_layer_genvk/layer_trait_generator.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from generator import OutputGenerator 16 | import reg 17 | import sys 18 | from typing import Optional 19 | from .vk_xml_util import ( 20 | VkXmlCommand, 21 | RustMethod, 22 | DispatchChainType, 23 | UnhandledCommand, 24 | generate_unhandled_command_comments, 25 | VulkanAliases, 26 | write_preamble, 27 | snake_case_to_upper_camel_case, 28 | ) 29 | 30 | 31 | class LayerTraitGenerator(OutputGenerator): 32 | def __init__(self, err_file=sys.stderr, warn_file=sys.stderr, diag_file=sys.stdout): 33 | super().__init__(err_file, warn_file, diag_file) 34 | self.all_commands: dict[DispatchChainType, dict[str, RustMethod]] = {} 35 | self.command_aliases: VulkanAliases = VulkanAliases() 36 | self.unhandled_commands: dict[str, UnhandledCommand] = {} 37 | self.types: dict[str, reg.TypeInfo | reg.GroupInfo] = {} 38 | 39 | def beginFile(self, gen_opts): 40 | super().beginFile(gen_opts) 41 | write_preamble(self.outFile) 42 | self.newline() 43 | self.outFile.write("// This file is generated from the Vulkan XML API registry.\n") 44 | self.outFile.write("#![allow(clippy::too_many_arguments)]\n") 45 | self.outFile.write( 46 | "\n".join( 47 | [ 48 | "use std::{ffi::{c_int, c_void}, mem::MaybeUninit};", 49 | "", 50 | "use ash::{vk, prelude::VkResult};", 51 | "", 52 | "use crate::VkLayerDeviceLink;", 53 | "use super::{LayerResult, TryFromVulkanCommandError};", 54 | ] 55 | ) 56 | ) 57 | self.newline() 58 | self.newline() 59 | 60 | def endFile(self): 61 | flat_all_commands: dict[str, RustMethod] = {} 62 | not_aliased_commands: set[str] = set() 63 | for commands in self.all_commands.values(): 64 | flat_all_commands |= commands 65 | for command in flat_all_commands: 66 | represent_name = self.command_aliases.get_represent_name(command) 67 | assert represent_name is not None, f"{command} is never added to the command aliases" 68 | if represent_name == command: 69 | not_aliased_commands.add(command) 70 | 71 | self.outFile.write(generate_unhandled_command_comments(self.unhandled_commands.values())) 72 | 73 | dispatch_chain_type_to_lines: dict[DispatchChainType, list[str]] = { 74 | DispatchChainType.INSTANCE: ["pub trait InstanceHooks: Send + Sync {"], 75 | DispatchChainType.DEVICE: ["pub trait DeviceHooks: Send + Sync {"], 76 | } 77 | command_enum = [ 78 | "#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Clone)]", 79 | "pub enum VulkanCommand {", 80 | ] 81 | try_from_command_impl = [ 82 | "impl TryFrom<&str> for VulkanCommand {", 83 | " type Error = TryFromVulkanCommandError;", 84 | "", 85 | " fn try_from(value: &str) -> Result {", 86 | " match value {", 87 | ] 88 | 89 | for dispatch_type, commands in self.all_commands.items(): 90 | for name, command in commands.items(): 91 | if name not in not_aliased_commands: 92 | continue 93 | hooks_trait_lines = dispatch_chain_type_to_lines.get(dispatch_type, None) 94 | if hooks_trait_lines is not None: 95 | hooks_trait_lines += [ 96 | f" {command.to_string()} {{", 97 | " LayerResult::Unhandled", 98 | " }", 99 | ] 100 | original_name = command.vk_xml_cmd.name 101 | enum_variant_name = snake_case_to_upper_camel_case(command.name) 102 | command_enum.append(f" {enum_variant_name},") 103 | try_from_command_impl.append( 104 | f' "{original_name}" => Ok(VulkanCommand::{enum_variant_name}),' 105 | ) 106 | command_enum += ["}"] 107 | try_from_command_impl += [ 108 | " _ => Err(TryFromVulkanCommandError::UnknownCommand(value.to_owned())),", 109 | " }", 110 | " }", 111 | "}", 112 | ] 113 | self.outFile.write("\n".join(command_enum)) 114 | self.newline() 115 | self.newline() 116 | self.outFile.write("\n".join(try_from_command_impl)) 117 | self.newline() 118 | self.newline() 119 | 120 | for dispatch_chain_type in sorted(list(dispatch_chain_type_to_lines.keys())): 121 | lines = dispatch_chain_type_to_lines[dispatch_chain_type] 122 | self.outFile.write("\n".join(lines)) 123 | self.newline() 124 | self.outFile.write("}\n") 125 | self.newline() 126 | 127 | super().endFile() 128 | 129 | def genCmd(self, cmdinfo: reg.CmdInfo, name: str, alias: Optional[str]): 130 | super().genCmd(cmdinfo, name, alias) 131 | 132 | unhandled_command = UnhandledCommand.find(name) 133 | if unhandled_command is not None: 134 | self.unhandled_commands[unhandled_command.name] = unhandled_command 135 | return 136 | 137 | should_skip = [ 138 | # Should be intercepted through Drop trait of InstanceInfo and DeviceInfo 139 | "vkDestroyInstance", 140 | "vkDestroyDevice", 141 | # Should be intercepted when the PhysicalDeviceInfo is created 142 | "vkEnumeratePhysicalDevices", 143 | "vkEnumeratePhysicalDeviceGroups", 144 | # Layer and extension properties should be provided via trait consts 145 | "vkEnumerateDeviceLayerProperties", 146 | "vkEnumerateDeviceExtensionProperties", 147 | # TODO: pre-instance functions are not supported now, but such support should be added. 148 | "vkEnumerateInstanceExtensionProperties", 149 | "vkEnumerateInstanceLayerProperties", 150 | "vkEnumerateInstanceVersion", 151 | ] 152 | if name in should_skip: 153 | return 154 | 155 | vk_xml_cmd = VkXmlCommand.from_cmd_info(cmdinfo, self.types) 156 | dispatch_chain_type = vk_xml_cmd.get_dispatch_chain_type() 157 | 158 | if dispatch_chain_type is None: 159 | self.unhandled_commands[name] = UnhandledCommand( 160 | name=name, reason="Unknown dispatch chain type" 161 | ) 162 | return 163 | 164 | self.command_aliases.add_alias(name, alias) 165 | commands = self.all_commands.setdefault(dispatch_chain_type, {}) 166 | rust_method = RustMethod.from_vk_xml_command(vk_xml_cmd) 167 | commands[name] = rust_method 168 | 169 | def genType(self, typeinfo: reg.TypeInfo, name: str, alias): 170 | super().genType(typeinfo, name, alias) 171 | self.types[name] = typeinfo 172 | 173 | def genGroup(self, groupinfo: reg.GroupInfo, groupName: str, alias): 174 | super().genGroup(groupinfo, groupName, alias) 175 | self.types[groupName] = groupinfo 176 | -------------------------------------------------------------------------------- /taplo.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | exclude = ["target/**", "third_party/**"] 16 | include = ["**/*.toml"] 17 | 18 | [formatting] 19 | align_comments = true 20 | align_entries = false 21 | align_single_comments = true 22 | allowed_blank_lines = 2 23 | array_trailing_comma = true 24 | column_width = 100 25 | compact_arrays = true 26 | compact_entries = false 27 | compact_inline_tables = false 28 | indent_entries = false 29 | indent_string = " " 30 | indent_tables = false 31 | reorder_arrays = false 32 | reorder_keys = true 33 | trailing_newline = true 34 | -------------------------------------------------------------------------------- /vulkan-layer-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | [package] 16 | edition = "2021" 17 | name = "vulkan-layer-macros" 18 | version = "0.1.0" 19 | 20 | [lib] 21 | proc-macro = true 22 | 23 | [dependencies] 24 | proc-macro2 = "1.0.69" 25 | quote = "1.0.36" 26 | syn = { version = "2.0.38", features = ["full"] } 27 | 28 | [dev-dependencies] 29 | ash = "0.37.3" 30 | once_cell = "1.17.1" 31 | vulkan-layer = { path = "../vulkan-layer" } 32 | -------------------------------------------------------------------------------- /vulkan-layer-macros/src/details.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use proc_macro2::TokenStream as TokenStream2; 16 | use quote::quote; 17 | use std::iter::zip; 18 | use syn::{spanned::Spanned, Error, Ident, ImplItem, ItemImpl, Type}; 19 | 20 | fn snake_case_to_upper_camel_case(input: &str) -> String { 21 | let first_char = match input.chars().next() { 22 | Some(first) => first, 23 | None => return input.to_owned(), 24 | }; 25 | let first_char = first_char.to_uppercase(); 26 | let mut res: String = first_char.to_string(); 27 | for (prev, (cur, next)) in zip( 28 | input.chars(), 29 | zip(input.chars().skip(1), input.chars().skip(2)), 30 | ) { 31 | if cur == '_' && next != '_' { 32 | continue; 33 | } 34 | if prev == '_' { 35 | res.push_str(&cur.to_uppercase().to_string()); 36 | continue; 37 | } 38 | res.push(cur); 39 | } 40 | res.push(input.chars().last().unwrap()); 41 | res 42 | } 43 | 44 | pub fn autoinfo(item: &ItemImpl, target_trait: &TokenStream2) -> Result { 45 | let type_name = item.self_ty.as_ref(); 46 | let hooked_commands = item.items.iter().filter_map(|item| { 47 | let function = if let ImplItem::Fn(function) = item { 48 | function 49 | } else { 50 | return None; 51 | }; 52 | let func_name = function.sig.ident.to_string(); 53 | let enum_variant_name = snake_case_to_upper_camel_case(&func_name); 54 | let enum_variant_name = Ident::new(&enum_variant_name, function.span()); 55 | Some(quote!(::vulkan_layer::LayerVulkanCommand::#enum_variant_name)) 56 | }); 57 | Ok(quote! { 58 | impl #target_trait for #type_name { 59 | type HooksType = Self; 60 | type HooksRefType<'a> = &'a Self; 61 | 62 | fn hooked_commands() -> &'static [::vulkan_layer::LayerVulkanCommand] { 63 | &[#(#hooked_commands),*] 64 | } 65 | 66 | fn hooks(&self) -> &Self { 67 | self 68 | } 69 | } 70 | }) 71 | } 72 | 73 | pub fn declare_introspection_queries_impl(global_type: &Type) -> Result { 74 | Ok(quote! { 75 | #[doc = "# Safety"] 76 | #[doc = ""] 77 | #[doc = "See valid usage of `vkEnumerateInstanceLayerProperties` at "] 78 | #[doc = ""] 79 | #[deny(unsafe_op_in_unsafe_fn)] 80 | #[no_mangle] 81 | pub unsafe extern "system" fn vkEnumerateInstanceLayerProperties( 82 | property_count: *mut u32, 83 | properties: *mut ::ash::vk::LayerProperties 84 | ) -> ::ash::vk::Result { 85 | // Safe because the caller is supposed to follow the exact same safety requirement. 86 | unsafe { #global_type::enumerate_instance_layer_properties(property_count, properties) } 87 | } 88 | 89 | #[doc = "# Safety"] 90 | #[doc = ""] 91 | #[doc = "See valid usage of `vkEnumerateInstanceExtensionProperties` at "] 92 | #[doc = ""] 93 | #[deny(unsafe_op_in_unsafe_fn)] 94 | #[no_mangle] 95 | pub unsafe extern "system" fn vkEnumerateInstanceExtensionProperties( 96 | layer_name: *const ::std::ffi::c_char, 97 | property_count: *mut u32, 98 | properties: *mut ::ash::vk::ExtensionProperties 99 | ) -> ::ash::vk::Result { 100 | // Safe because the caller is supposed to follow the exact same safety requirement. 101 | unsafe { 102 | #global_type::enumerate_instance_extension_properties( 103 | layer_name, 104 | property_count, 105 | properties, 106 | ) 107 | } 108 | } 109 | 110 | #[doc = "# Safety"] 111 | #[doc = ""] 112 | #[doc = "See valid usage of `vkEnumerateDeviceLayerProperties` at "] 113 | #[doc = ""] 114 | #[deny(unsafe_op_in_unsafe_fn)] 115 | #[no_mangle] 116 | pub unsafe extern "system" fn vkEnumerateDeviceLayerProperties( 117 | physical_device: ::ash::vk::PhysicalDevice, 118 | p_property_count: *mut u32, 119 | p_properties: *mut ::ash::vk::LayerProperties, 120 | ) -> ::ash::vk::Result { 121 | // Safe, because the caller is supposed to follow the exact same safety requirement. 122 | unsafe { 123 | #global_type::enumerate_device_layer_properties( 124 | physical_device, 125 | p_property_count, 126 | p_properties, 127 | ) 128 | } 129 | } 130 | 131 | #[doc = "# Safety"] 132 | #[doc = ""] 133 | #[doc = "See valid usage of `vkEnumerateDeviceLayerProperties` at "] 134 | #[doc = ""] 135 | #[deny(unsafe_op_in_unsafe_fn)] 136 | #[no_mangle] 137 | pub unsafe extern "system" fn vkEnumerateDeviceExtensionProperties( 138 | physical_device: ::ash::vk::PhysicalDevice, 139 | p_layer_name: *const ::std::ffi::c_char, 140 | p_property_count: *mut u32, 141 | p_properties: *mut ::ash::vk::ExtensionProperties, 142 | ) -> ::ash::vk::Result { 143 | // Safe, because the caller is supposed to follow the exact same safety requirement. 144 | unsafe { 145 | #global_type::enumerate_device_extension_properties( 146 | physical_device, 147 | p_layer_name, 148 | p_property_count, 149 | p_properties, 150 | ) 151 | } 152 | } 153 | 154 | #[doc = "# Safety"] 155 | #[doc = ""] 156 | #[doc = "See valid usage of `vkGetInstanceProcAddr` at "] 157 | #[doc = ""] 158 | #[deny(unsafe_op_in_unsafe_fn)] 159 | #[no_mangle] 160 | pub unsafe extern "system" fn vkGetInstanceProcAddr( 161 | instance: ::ash::vk::Instance, 162 | p_name: *const ::std::ffi::c_char, 163 | ) -> ::ash::vk::PFN_vkVoidFunction { 164 | // Safe, because the caller is supposed to follow the exact same safety requirement. 165 | unsafe { 166 | #global_type::get_instance_proc_addr(instance, p_name) 167 | } 168 | } 169 | 170 | #[doc = "# Safety"] 171 | #[doc = ""] 172 | #[doc = "See valid usage of `vkGetDeviceProcAddr` at "] 173 | #[doc = ""] 174 | #[deny(unsafe_op_in_unsafe_fn)] 175 | #[no_mangle] 176 | pub unsafe extern "system" fn vkGetDeviceProcAddr( 177 | device: ::ash::vk::Device, 178 | p_name: *const ::std::ffi::c_char, 179 | ) -> ::ash::vk::PFN_vkVoidFunction { 180 | // Safe, because the caller is supposed to follow the exact same safety requirement. 181 | unsafe { 182 | #global_type::get_device_proc_addr(device, p_name) 183 | } 184 | } 185 | }) 186 | } 187 | -------------------------------------------------------------------------------- /vulkan-layer-macros/src/dummy.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use proc_macro2::TokenStream as TokenStream2; 16 | use quote::quote; 17 | use syn::Type; 18 | 19 | pub fn dummy_autoinfo_impl(name: &Type, target_trait: &TokenStream2) -> TokenStream2 { 20 | quote! { 21 | impl #target_trait for #name { 22 | type HooksType = Self; 23 | type HooksRefType<'a> = &'a Self; 24 | fn hooked_commands() -> &'static [::vulkan_layer::LayerVulkanCommand] { 25 | unimplemented!() 26 | } 27 | 28 | fn hooks(&self) -> Self::HooksRefType<'_> { 29 | unimplemented!() 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /vulkan-layer-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #![warn(missing_docs)] 16 | 17 | //! Macros for `vulkan-layer`. 18 | 19 | use proc_macro::TokenStream; 20 | use proc_macro2::TokenStream as TokenStream2; 21 | use quote::quote; 22 | use syn::{parse_macro_input, ItemImpl, Type}; 23 | 24 | mod details; 25 | mod dummy; 26 | 27 | /// Derive the implementation of the `vulkan_layer::GlobalHooksInfo` trait from the implementation 28 | /// of the `vulkan_layer::GlobalHooks` trait. 29 | /// 30 | /// This attribute macro should be used over an implementation item of the 31 | /// `vulkan_layer::GlobalHooks` trait, and will implement the `vulkan_layer::GlobalHooksInfo` trait 32 | /// for the type: 33 | /// * `GlobalHooksInfo::hooked_commands` returns a list of the overridden methods that appear in the 34 | /// implementation item. 35 | /// * `GlobalHooksInfo::HooksType` and `GlobalHooksInfo::HooksRefType` are defined as `Self` and 36 | /// `&Self`. 37 | /// * `GlobalHooksInfo::hooks` returns `self`. 38 | /// 39 | /// # Examples 40 | /// 41 | /// ``` 42 | /// use ash::vk; 43 | /// use vulkan_layer::{ 44 | /// auto_globalhooksinfo_impl, GlobalHooks, GlobalHooksInfo, LayerResult, LayerVulkanCommand, 45 | /// VkLayerInstanceLink, 46 | /// }; 47 | /// 48 | /// #[derive(Default)] 49 | /// struct MyGlobalHooks; 50 | /// 51 | /// #[auto_globalhooksinfo_impl] 52 | /// impl GlobalHooks for MyGlobalHooks { 53 | /// fn create_instance( 54 | /// &self, 55 | /// _p_create_info: &vk::InstanceCreateInfo, 56 | /// _layer_instance_link: &VkLayerInstanceLink, 57 | /// _p_allocator: Option<&vk::AllocationCallbacks>, 58 | /// _p_instance: *mut vk::Instance, 59 | /// ) -> LayerResult> { 60 | /// LayerResult::Unhandled 61 | /// } 62 | /// } 63 | /// 64 | /// let my_global_hooks: MyGlobalHooks = Default::default(); 65 | /// assert!(std::ptr::eq(&my_global_hooks, my_global_hooks.hooks())); 66 | /// assert_eq!( 67 | /// MyGlobalHooks::hooked_commands(), 68 | /// [LayerVulkanCommand::CreateInstance] 69 | /// ); 70 | /// ``` 71 | #[proc_macro_attribute] 72 | pub fn auto_globalhooksinfo_impl(_: TokenStream, item: TokenStream) -> TokenStream { 73 | let original_item: TokenStream2 = item.clone().into(); 74 | let input = parse_macro_input!(item as ItemImpl); 75 | let target_trait = quote!(::vulkan_layer::GlobalHooksInfo); 76 | let to_append = details::autoinfo(&input, &target_trait).unwrap_or_else(|e| { 77 | let dummy = dummy::dummy_autoinfo_impl(&input.self_ty, &target_trait); 78 | let compile_error = e.to_compile_error(); 79 | quote! { 80 | #dummy 81 | #compile_error 82 | } 83 | }); 84 | quote! { 85 | #original_item 86 | #to_append 87 | } 88 | .into() 89 | } 90 | 91 | /// Derive the implementation of the `vulkan_layer::InstanceInfo` trait from the implementation of 92 | /// the `vulkan_layer::InstanceHooks`. 93 | /// 94 | /// This attribute macro should be used over an implementation item of the 95 | /// `vulkan_layer::InstanceHooks` trait, and will implement the `vulkan_layer::InstanceInfo` trait 96 | /// for the type: 97 | /// * `InstanceInfo::hooked_commands` returns a list of the overridden methods that appear in the 98 | /// implementation item. 99 | /// * `InstanceInfo::HooksType` and `InstanceInfo::HooksRefType` are defined as `Self` and `&Self`. 100 | /// * `InstanceInfo::hooks` returns `self`. 101 | /// 102 | /// # Examples 103 | /// 104 | /// ``` 105 | /// use ash::vk; 106 | /// use std::mem::MaybeUninit; 107 | /// use vulkan_layer::{ 108 | /// auto_instanceinfo_impl, InstanceHooks, InstanceInfo, LayerResult, LayerVulkanCommand, 109 | /// }; 110 | /// 111 | /// #[derive(Default)] 112 | /// struct MyInstanceHooks; 113 | /// 114 | /// #[auto_instanceinfo_impl] 115 | /// impl InstanceHooks for MyInstanceHooks { 116 | /// fn get_physical_device_features( 117 | /// &self, 118 | /// _physical_device: vk::PhysicalDevice, 119 | /// _p_features: &mut MaybeUninit, 120 | /// ) -> LayerResult<()> { 121 | /// LayerResult::Unhandled 122 | /// } 123 | /// } 124 | /// 125 | /// let my_instance_hooks: MyInstanceHooks = Default::default(); 126 | /// assert!(std::ptr::eq(my_instance_hooks.hooks(), &my_instance_hooks)); 127 | /// assert_eq!( 128 | /// MyInstanceHooks::hooked_commands(), 129 | /// [LayerVulkanCommand::GetPhysicalDeviceFeatures] 130 | /// ); 131 | /// ``` 132 | #[proc_macro_attribute] 133 | pub fn auto_instanceinfo_impl(_: TokenStream, item: TokenStream) -> TokenStream { 134 | let original_item: TokenStream2 = item.clone().into(); 135 | let input = parse_macro_input!(item as ItemImpl); 136 | let target_trait = quote!(::vulkan_layer::InstanceInfo); 137 | let to_append = details::autoinfo(&input, &target_trait).unwrap_or_else(|e| { 138 | let dummy = dummy::dummy_autoinfo_impl(&input.self_ty, &target_trait); 139 | let compile_error = e.to_compile_error(); 140 | quote! { 141 | #dummy 142 | #compile_error 143 | } 144 | }); 145 | quote! { 146 | #original_item 147 | #to_append 148 | } 149 | .into() 150 | } 151 | 152 | /// Derive the implementation of the `vulkan_layer::DeviceInfo` trait from the implementation of the 153 | /// `vulkan_layer::DeviceHooks` trait. 154 | /// 155 | /// This attribute macro should be used over an implementation item of the 156 | /// `vulkan_layer::DeviceHooks` trait, and will implement the `vulkan_layer::DeviceInfo` trait for 157 | /// the type: 158 | /// * `DeviceInfo::hooked_commands` returns a list of the overridden methods that appear in the 159 | /// implementation item. 160 | /// * `DeviceInfo::HooksType` and `DeviceInfo::HooksRefType` are defined as `Self` and `&Self`. 161 | /// * `DeviceInfo::hooks` returns `self`. 162 | /// 163 | /// # Examples 164 | /// 165 | /// ``` 166 | /// use ash::vk; 167 | /// use vulkan_layer::{ 168 | /// auto_deviceinfo_impl, DeviceHooks, DeviceInfo, LayerResult, LayerVulkanCommand, 169 | /// }; 170 | /// 171 | /// #[derive(Default)] 172 | /// struct MyDeviceHooks; 173 | /// 174 | /// #[auto_deviceinfo_impl] 175 | /// impl DeviceHooks for MyDeviceHooks { 176 | /// fn create_image( 177 | /// &self, 178 | /// _p_create_info: &vk::ImageCreateInfo, 179 | /// _p_allocator: Option<&vk::AllocationCallbacks>, 180 | /// ) -> LayerResult> { 181 | /// LayerResult::Unhandled 182 | /// } 183 | /// } 184 | /// 185 | /// let my_device_hooks: MyDeviceHooks = Default::default(); 186 | /// assert!(std::ptr::eq(my_device_hooks.hooks(), &my_device_hooks)); 187 | /// assert_eq!( 188 | /// MyDeviceHooks::hooked_commands(), 189 | /// [LayerVulkanCommand::CreateImage] 190 | /// ); 191 | /// ``` 192 | #[proc_macro_attribute] 193 | pub fn auto_deviceinfo_impl(_: TokenStream, item: TokenStream) -> TokenStream { 194 | let original_item: TokenStream2 = item.clone().into(); 195 | let input = parse_macro_input!(item as ItemImpl); 196 | let target_trait = quote!(::vulkan_layer::DeviceInfo); 197 | let to_append = details::autoinfo(&input, &target_trait).unwrap_or_else(|e| { 198 | let dummy = dummy::dummy_autoinfo_impl(&input.self_ty, &target_trait); 199 | let compile_error = e.to_compile_error(); 200 | quote! { 201 | #dummy 202 | #compile_error 203 | } 204 | }); 205 | quote! { 206 | #original_item 207 | #to_append 208 | } 209 | .into() 210 | } 211 | 212 | /// Declare the required introspection queries for Android given an instantiated 213 | /// `vulkan_layer::Global` type. 214 | /// 215 | /// All functions are defined without name mangling, so that they are exported as C symbols in the 216 | /// generated dynamic library. This is recommended by 217 | /// [the Vulkan loader doc](https://github.com/KhronosGroup/Vulkan-Loader/blob/280997da523951c4016f4ca6af66d58a31e36ab3/docs/LoaderLayerInterface.md#layer-manifest-file-usage:~:text=These%20introspection%20functions%20are%20not%20used%20by%20the%20Khronos%20loader%20but%20should%20be%20present%20in%20layers%20to%20maintain%20consistency.%20The%20specific%20%22introspection%22%20functions%20are%20called%20out%20in%20the%20Layer%20Manifest%20File%20Format%20table): 218 | /// 219 | /// > These introspection functions are not used by the Khronos loader but should be present in 220 | /// > layers to maintain consistency. The specific "introspection" functions are called out in the 221 | /// > Layer Manifest File Format table. 222 | /// 223 | /// According to the 224 | /// [the Vulkan loader doc](https://github.com/KhronosGroup/Vulkan-Loader/blob/280997da523951c4016f4ca6af66d58a31e36ab3/docs/LoaderLayerInterface.md#layer-manifest-file-format), introspection queries include: 225 | /// * `vkEnumerateInstanceLayerProperties` 226 | /// * `vkEnumerateInstanceExtensionProperties` 227 | /// * `vkEnumerateDeviceLayerProperties` 228 | /// * `vkEnumerateDeviceExtensionProperties` 229 | /// * `vkGetInstanceProcAddr` 230 | /// * `vkGetDeviceProcAddr` 231 | /// # Examples 232 | /// 233 | /// ``` 234 | /// # use std::sync::Arc; 235 | /// # use vulkan_layer::{StubGlobalHooks, StubInstanceInfo, StubDeviceInfo, Layer, Global, declare_introspection_queries, LayerManifest}; 236 | /// # use once_cell::sync::Lazy; 237 | /// # use ash::{vk, self}; 238 | /// # 239 | /// #[derive(Default)] 240 | /// struct MyLayer(StubGlobalHooks); 241 | /// 242 | /// impl Layer for MyLayer { 243 | /// // ... 244 | /// # type GlobalHooksInfo = StubGlobalHooks; 245 | /// # type InstanceInfo = StubInstanceInfo; 246 | /// # type DeviceInfo = StubDeviceInfo; 247 | /// # type InstanceInfoContainer = StubInstanceInfo; 248 | /// # type DeviceInfoContainer = StubDeviceInfo; 249 | /// # 250 | /// # fn global_instance() -> impl std::ops::Deref> + 'static { 251 | /// # static GLOBAL: Lazy> = Lazy::new(Default::default); 252 | /// # &*GLOBAL 253 | /// # } 254 | /// # 255 | /// # fn manifest() -> LayerManifest { 256 | /// # Default::default() 257 | /// # } 258 | /// # 259 | /// # fn global_hooks_info(&self) -> &Self::GlobalHooksInfo { 260 | /// # &self.0 261 | /// # } 262 | /// # 263 | /// # fn create_instance_info( 264 | /// # &self, 265 | /// # _: &vk::InstanceCreateInfo, 266 | /// # _: Option<&vk::AllocationCallbacks>, 267 | /// # _: Arc, 268 | /// # _next_get_instance_proc_addr: vk::PFN_vkGetInstanceProcAddr, 269 | /// # ) -> Self::InstanceInfoContainer { 270 | /// # Default::default() 271 | /// # } 272 | /// # 273 | /// # fn create_device_info( 274 | /// # &self, 275 | /// # _: vk::PhysicalDevice, 276 | /// # _: &vk::DeviceCreateInfo, 277 | /// # _: Option<&vk::AllocationCallbacks>, 278 | /// # _: Arc, 279 | /// # _next_get_device_proc_addr: vk::PFN_vkGetDeviceProcAddr, 280 | /// # ) -> Self::DeviceInfoContainer { 281 | /// # Default::default() 282 | /// # } 283 | /// } 284 | /// 285 | /// type MyGlobal = Global::; 286 | /// declare_introspection_queries!(MyGlobal); 287 | /// # let _: vk::PFN_vkEnumerateInstanceLayerProperties = vkEnumerateInstanceLayerProperties; 288 | /// # let _: vk::PFN_vkEnumerateInstanceExtensionProperties = vkEnumerateInstanceExtensionProperties; 289 | /// # let _: vk::PFN_vkEnumerateDeviceLayerProperties = vkEnumerateDeviceLayerProperties; 290 | /// # let _: vk::PFN_vkEnumerateDeviceExtensionProperties = vkEnumerateDeviceExtensionProperties; 291 | /// # let _: vk::PFN_vkGetInstanceProcAddr = vkGetInstanceProcAddr; 292 | /// # let _: vk::PFN_vkGetDeviceProcAddr = vkGetDeviceProcAddr; 293 | /// ``` 294 | #[proc_macro] 295 | pub fn declare_introspection_queries(item: TokenStream) -> TokenStream { 296 | let global_type = parse_macro_input!(item as Type); 297 | details::declare_introspection_queries_impl(&global_type) 298 | .unwrap_or_else(|e| e.to_compile_error()) 299 | .into() 300 | } 301 | -------------------------------------------------------------------------------- /vulkan-layer/Android.bp: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package { 16 | default_applicable_licenses: ["external_nexus_rust_vk_layer_license"], 17 | } 18 | 19 | rust_library { 20 | name: "libvulkan_layer_for_rust", 21 | crate_name: "vulkan_layer", 22 | srcs: ["src/lib.rs"], 23 | rustlibs: [ 24 | "libash_rust", 25 | "liblog_rust", 26 | "liblogger", 27 | "libnum_traits", 28 | "libonce_cell", 29 | ], 30 | prefer_rlib: true, 31 | } 32 | 33 | -------------------------------------------------------------------------------- /vulkan-layer/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | [package] 16 | edition = "2021" 17 | name = "vulkan-layer" 18 | version = "0.1.0" 19 | 20 | [dependencies] 21 | ash = "0.37.3" 22 | bytemuck = "1.14.1" 23 | cfg-if = "1.0.0" 24 | log = "0.4.17" 25 | mockall = { version = "0.12.1", optional = true } 26 | num-traits = "0.2.17" 27 | once_cell = "1.17.1" 28 | smallvec = "1.13.1" 29 | thiserror = "1.0.49" 30 | vulkan-layer-macros = { path = "../vulkan-layer-macros" } 31 | 32 | [features] 33 | _test = ["dep:mockall", "unstable"] 34 | default = [] 35 | unstable = [] 36 | 37 | [dev-dependencies] 38 | env_logger = "0.11.3" 39 | parking_lot = "0.12.3" 40 | vulkan-layer = { path = ".", default-features = false, features = ["_test"] } 41 | 42 | [build-dependencies] 43 | rustc_version = "0.4.0" 44 | 45 | [lints.rust] 46 | unexpected_cfgs = { level = "warn", check-cfg = ['cfg(RUSTC_NIGHTLY)'] } 47 | -------------------------------------------------------------------------------- /vulkan-layer/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | fn main() { 16 | let channel = rustc_version::version_meta() 17 | .map(|meta| meta.channel) 18 | .unwrap_or(rustc_version::Channel::Stable); 19 | use rustc_version::Channel::*; 20 | let channel = match channel { 21 | Stable => "RUSTC_STABLE", 22 | Beta => "RUSTC_BETA", 23 | Nightly => "RUSTC_NIGHTLY", 24 | Dev => "RUSTC_DEV", 25 | }; 26 | println!("cargo:rustc-cfg={}", channel); 27 | } 28 | -------------------------------------------------------------------------------- /vulkan-layer/src/bindings.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | pub mod vk_layer; 15 | -------------------------------------------------------------------------------- /vulkan-layer/src/bindings/vk_layer.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #![allow(non_camel_case_types)] 16 | #![allow(non_snake_case)] 17 | #![allow(non_upper_case_globals)] 18 | #![allow(dead_code)] 19 | 20 | use std::mem::MaybeUninit; 21 | 22 | use ash::vk; 23 | 24 | mod generated; 25 | 26 | /// Bindings for the C `PFN_vkLayerCreateDevice` type defined in the `vk_layer.h` file. 27 | pub use generated::PFN_vkLayerCreateDevice; 28 | /// Bindings for the C `PFN_vkLayerDestroyDevice` type defined in the `vk_layer.h` file. 29 | pub use generated::PFN_vkLayerDestroyDevice; 30 | /// Bindings for the C `PFN_vkSetInstanceLoaderData` type defined in the `vk_layer.h` file. 31 | pub use generated::PFN_vkSetInstanceLoaderData; 32 | /// Sub type of structure for instance and device loader ext of CreateInfo. Bindings for the C 33 | /// `VkLayerDeviceCreateInfo` type defined in the `vk_layer.h` file. 34 | /// 35 | /// When `sType` is 36 | /// [`VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkStructureType.html) 37 | /// then [`VkLayerFunction`] indicates struct type pointed to by pNext. 38 | pub use generated::VkLayerDeviceCreateInfo; 39 | /// A list node that contains the next entity's `vkGetInstanceProcAddr` and 40 | /// `vkGetDeviceProcAddr` used by a layer. One possible payload of [`VkLayerDeviceCreateInfo`] 41 | pub use generated::VkLayerDeviceLink; 42 | /// Bindings for the C `VkLayerFunction` type defined in the `vk_layer.h` file. 43 | /// 44 | /// Used to distinguish the payload of the loader extension of CreateInfo: 45 | /// [`VkLayerDeviceCreateInfo`], [`VkLayerInstanceCreateInfo`]. 46 | pub use generated::VkLayerFunction; 47 | /// A list node that contains the next entity's vkGetInstanceProcAddr used by a layer. One 48 | /// possible payload of [`VkLayerInstanceCreateInfo`]. 49 | pub use generated::VkLayerInstanceLink; 50 | 51 | type VkInstance = vk::Instance; 52 | type VkPhysicalDevice = vk::PhysicalDevice; 53 | type VkDevice = vk::Device; 54 | type VkStructureType = vk::StructureType; 55 | type VkResult = vk::Result; 56 | type VkDeviceCreateInfo = vk::DeviceCreateInfo; 57 | type VkAllocationCallbacks = vk::AllocationCallbacks; 58 | 59 | #[repr(transparent)] 60 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 61 | pub struct VkLoaderFeatureFlags(vk::Flags); 62 | ash::vk_bitflags_wrapped!(VkLoaderFeatureFlags, vk::Flags); 63 | impl VkLoaderFeatureFlags { 64 | pub const _PHYSICAL_DEVICE_SORTING: Self = Self(0x00000001); 65 | } 66 | 67 | #[repr(C)] 68 | #[derive(Debug, Default, Copy, Clone)] 69 | pub struct VkLayerInstanceCreateInfoUFieldLayerDeviceField { 70 | pub pfnLayerCreateDevice: PFN_vkLayerCreateDevice, 71 | pub pfnLayerDestroyDevice: PFN_vkLayerDestroyDevice, 72 | } 73 | 74 | #[repr(C)] 75 | #[derive(Copy, Clone)] 76 | pub union VkLayerInstanceCreateInfoUField { 77 | pub pLayerInfo: *mut VkLayerInstanceLink, 78 | pub pfnSetInstanceLoaderData: PFN_vkSetInstanceLoaderData, 79 | pub layerDevice: VkLayerInstanceCreateInfoUFieldLayerDeviceField, 80 | pub loaderFeatures: VkLoaderFeatureFlags, 81 | } 82 | 83 | impl Default for VkLayerInstanceCreateInfoUField { 84 | fn default() -> Self { 85 | let mut s = MaybeUninit::::uninit(); 86 | unsafe { 87 | std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); 88 | s.assume_init() 89 | } 90 | } 91 | } 92 | 93 | #[repr(C)] 94 | /// Sub type of structure for instance and device loader ext of CreateInfo. 95 | /// 96 | /// When `sType` is 97 | /// [`VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkStructureType.html) 98 | /// then [`VkLayerFunction`] indicates struct type pointed to by pNext. 99 | pub struct VkLayerInstanceCreateInfo { 100 | /// A `VkStructureType` value identifying this struct. Must be 101 | /// `VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO`. 102 | pub sType: VkStructureType, 103 | /// Either `NULL` or a pointer to a structure extending this structure. 104 | pub pNext: *const ::std::os::raw::c_void, 105 | /// A [`VkLayerFunction`] value identifying the payload in the `u` field. 106 | pub function: VkLayerFunction, 107 | /// The actual payload. 108 | pub u: VkLayerInstanceCreateInfoUField, 109 | } 110 | 111 | unsafe impl vk::TaggedStructure for VkLayerInstanceCreateInfo { 112 | const STRUCTURE_TYPE: vk::StructureType = vk::StructureType::LOADER_INSTANCE_CREATE_INFO; 113 | } 114 | 115 | unsafe impl vk::ExtendsInstanceCreateInfo for VkLayerInstanceCreateInfo {} 116 | 117 | unsafe impl vk::TaggedStructure for VkLayerDeviceCreateInfo { 118 | const STRUCTURE_TYPE: vk::StructureType = vk::StructureType::LOADER_DEVICE_CREATE_INFO; 119 | } 120 | 121 | unsafe impl vk::ExtendsDeviceCreateInfo for VkLayerDeviceCreateInfo {} 122 | -------------------------------------------------------------------------------- /vulkan-layer/src/bindings/vk_layer/generated.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #![allow(missing_docs)] 16 | use super::*; 17 | use ash::vk::*; 18 | #[cfg(unix)] 19 | mod unix; 20 | #[cfg(windows)] 21 | mod windows; 22 | #[cfg(unix)] 23 | pub use unix::*; 24 | #[cfg(windows)] 25 | pub use windows::*; 26 | /* automatically generated by rust-bindgen 0.69.4 */ 27 | 28 | pub type PFN_GetPhysicalDeviceProcAddr = ::std::option::Option< 29 | unsafe extern "C" fn( 30 | instance: VkInstance, 31 | pName: *const ::std::os::raw::c_char, 32 | ) -> PFN_vkVoidFunction, 33 | >; 34 | #[repr(C)] 35 | pub struct VkNegotiateLayerInterface { 36 | pub sType: VkNegotiateLayerStructType, 37 | pub pNext: *mut ::std::os::raw::c_void, 38 | pub loaderLayerInterfaceVersion: u32, 39 | pub pfnGetInstanceProcAddr: PFN_vkGetInstanceProcAddr, 40 | pub pfnGetDeviceProcAddr: PFN_vkGetDeviceProcAddr, 41 | pub pfnGetPhysicalDeviceProcAddr: PFN_GetPhysicalDeviceProcAddr, 42 | } 43 | #[test] 44 | fn bindgen_test_layout_VkNegotiateLayerInterface() { 45 | const UNINIT: ::std::mem::MaybeUninit = 46 | ::std::mem::MaybeUninit::uninit(); 47 | let ptr = UNINIT.as_ptr(); 48 | assert_eq!( 49 | ::std::mem::size_of::(), 50 | 48usize, 51 | concat!("Size of: ", stringify!(VkNegotiateLayerInterface)) 52 | ); 53 | assert_eq!( 54 | ::std::mem::align_of::(), 55 | 8usize, 56 | concat!("Alignment of ", stringify!(VkNegotiateLayerInterface)) 57 | ); 58 | assert_eq!( 59 | unsafe { ::std::ptr::addr_of!((*ptr).sType) as usize - ptr as usize }, 60 | 0usize, 61 | concat!( 62 | "Offset of field: ", 63 | stringify!(VkNegotiateLayerInterface), 64 | "::", 65 | stringify!(sType) 66 | ) 67 | ); 68 | assert_eq!( 69 | unsafe { ::std::ptr::addr_of!((*ptr).pNext) as usize - ptr as usize }, 70 | 8usize, 71 | concat!( 72 | "Offset of field: ", 73 | stringify!(VkNegotiateLayerInterface), 74 | "::", 75 | stringify!(pNext) 76 | ) 77 | ); 78 | assert_eq!( 79 | unsafe { ::std::ptr::addr_of!((*ptr).loaderLayerInterfaceVersion) as usize - ptr as usize }, 80 | 16usize, 81 | concat!( 82 | "Offset of field: ", 83 | stringify!(VkNegotiateLayerInterface), 84 | "::", 85 | stringify!(loaderLayerInterfaceVersion) 86 | ) 87 | ); 88 | assert_eq!( 89 | unsafe { ::std::ptr::addr_of!((*ptr).pfnGetInstanceProcAddr) as usize - ptr as usize }, 90 | 24usize, 91 | concat!( 92 | "Offset of field: ", 93 | stringify!(VkNegotiateLayerInterface), 94 | "::", 95 | stringify!(pfnGetInstanceProcAddr) 96 | ) 97 | ); 98 | assert_eq!( 99 | unsafe { ::std::ptr::addr_of!((*ptr).pfnGetDeviceProcAddr) as usize - ptr as usize }, 100 | 32usize, 101 | concat!( 102 | "Offset of field: ", 103 | stringify!(VkNegotiateLayerInterface), 104 | "::", 105 | stringify!(pfnGetDeviceProcAddr) 106 | ) 107 | ); 108 | assert_eq!( 109 | unsafe { 110 | ::std::ptr::addr_of!((*ptr).pfnGetPhysicalDeviceProcAddr) as usize - ptr as usize 111 | }, 112 | 40usize, 113 | concat!( 114 | "Offset of field: ", 115 | stringify!(VkNegotiateLayerInterface), 116 | "::", 117 | stringify!(pfnGetPhysicalDeviceProcAddr) 118 | ) 119 | ); 120 | } 121 | impl Default for VkNegotiateLayerInterface { 122 | fn default() -> Self { 123 | let mut s = ::std::mem::MaybeUninit::::uninit(); 124 | unsafe { 125 | ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); 126 | s.assume_init() 127 | } 128 | } 129 | } 130 | #[repr(C)] 131 | pub struct VkLayerInstanceLink_ { 132 | pub pNext: *mut VkLayerInstanceLink_, 133 | pub pfnNextGetInstanceProcAddr: PFN_vkGetInstanceProcAddr, 134 | pub pfnNextGetPhysicalDeviceProcAddr: PFN_GetPhysicalDeviceProcAddr, 135 | } 136 | #[test] 137 | fn bindgen_test_layout_VkLayerInstanceLink_() { 138 | const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); 139 | let ptr = UNINIT.as_ptr(); 140 | assert_eq!( 141 | ::std::mem::size_of::(), 142 | 24usize, 143 | concat!("Size of: ", stringify!(VkLayerInstanceLink_)) 144 | ); 145 | assert_eq!( 146 | ::std::mem::align_of::(), 147 | 8usize, 148 | concat!("Alignment of ", stringify!(VkLayerInstanceLink_)) 149 | ); 150 | assert_eq!( 151 | unsafe { ::std::ptr::addr_of!((*ptr).pNext) as usize - ptr as usize }, 152 | 0usize, 153 | concat!( 154 | "Offset of field: ", 155 | stringify!(VkLayerInstanceLink_), 156 | "::", 157 | stringify!(pNext) 158 | ) 159 | ); 160 | assert_eq!( 161 | unsafe { ::std::ptr::addr_of!((*ptr).pfnNextGetInstanceProcAddr) as usize - ptr as usize }, 162 | 8usize, 163 | concat!( 164 | "Offset of field: ", 165 | stringify!(VkLayerInstanceLink_), 166 | "::", 167 | stringify!(pfnNextGetInstanceProcAddr) 168 | ) 169 | ); 170 | assert_eq!( 171 | unsafe { 172 | ::std::ptr::addr_of!((*ptr).pfnNextGetPhysicalDeviceProcAddr) as usize - ptr as usize 173 | }, 174 | 16usize, 175 | concat!( 176 | "Offset of field: ", 177 | stringify!(VkLayerInstanceLink_), 178 | "::", 179 | stringify!(pfnNextGetPhysicalDeviceProcAddr) 180 | ) 181 | ); 182 | } 183 | impl Default for VkLayerInstanceLink_ { 184 | fn default() -> Self { 185 | let mut s = ::std::mem::MaybeUninit::::uninit(); 186 | unsafe { 187 | ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); 188 | s.assume_init() 189 | } 190 | } 191 | } 192 | pub type VkLayerInstanceLink = VkLayerInstanceLink_; 193 | pub type PFN_vkSetInstanceLoaderData = ::std::option::Option< 194 | unsafe extern "C" fn(instance: VkInstance, object: *mut ::std::os::raw::c_void) -> VkResult, 195 | >; 196 | pub type PFN_vkSetDeviceLoaderData = ::std::option::Option< 197 | unsafe extern "C" fn(device: VkDevice, object: *mut ::std::os::raw::c_void) -> VkResult, 198 | >; 199 | pub type PFN_vkLayerCreateDevice = ::std::option::Option< 200 | unsafe extern "C" fn( 201 | instance: VkInstance, 202 | physicalDevice: VkPhysicalDevice, 203 | pCreateInfo: *const VkDeviceCreateInfo, 204 | pAllocator: *const VkAllocationCallbacks, 205 | pDevice: *mut VkDevice, 206 | layerGIPA: PFN_vkGetInstanceProcAddr, 207 | nextGDPA: *mut PFN_vkGetDeviceProcAddr, 208 | ) -> VkResult, 209 | >; 210 | pub type PFN_vkLayerDestroyDevice = ::std::option::Option< 211 | unsafe extern "C" fn( 212 | physicalDevice: VkDevice, 213 | pAllocator: *const VkAllocationCallbacks, 214 | destroyFunction: PFN_vkDestroyDevice, 215 | ), 216 | >; 217 | #[repr(C)] 218 | pub struct VkLayerDeviceLink_ { 219 | pub pNext: *mut VkLayerDeviceLink_, 220 | pub pfnNextGetInstanceProcAddr: PFN_vkGetInstanceProcAddr, 221 | pub pfnNextGetDeviceProcAddr: PFN_vkGetDeviceProcAddr, 222 | } 223 | #[test] 224 | fn bindgen_test_layout_VkLayerDeviceLink_() { 225 | const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); 226 | let ptr = UNINIT.as_ptr(); 227 | assert_eq!( 228 | ::std::mem::size_of::(), 229 | 24usize, 230 | concat!("Size of: ", stringify!(VkLayerDeviceLink_)) 231 | ); 232 | assert_eq!( 233 | ::std::mem::align_of::(), 234 | 8usize, 235 | concat!("Alignment of ", stringify!(VkLayerDeviceLink_)) 236 | ); 237 | assert_eq!( 238 | unsafe { ::std::ptr::addr_of!((*ptr).pNext) as usize - ptr as usize }, 239 | 0usize, 240 | concat!( 241 | "Offset of field: ", 242 | stringify!(VkLayerDeviceLink_), 243 | "::", 244 | stringify!(pNext) 245 | ) 246 | ); 247 | assert_eq!( 248 | unsafe { ::std::ptr::addr_of!((*ptr).pfnNextGetInstanceProcAddr) as usize - ptr as usize }, 249 | 8usize, 250 | concat!( 251 | "Offset of field: ", 252 | stringify!(VkLayerDeviceLink_), 253 | "::", 254 | stringify!(pfnNextGetInstanceProcAddr) 255 | ) 256 | ); 257 | assert_eq!( 258 | unsafe { ::std::ptr::addr_of!((*ptr).pfnNextGetDeviceProcAddr) as usize - ptr as usize }, 259 | 16usize, 260 | concat!( 261 | "Offset of field: ", 262 | stringify!(VkLayerDeviceLink_), 263 | "::", 264 | stringify!(pfnNextGetDeviceProcAddr) 265 | ) 266 | ); 267 | } 268 | impl Default for VkLayerDeviceLink_ { 269 | fn default() -> Self { 270 | let mut s = ::std::mem::MaybeUninit::::uninit(); 271 | unsafe { 272 | ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); 273 | s.assume_init() 274 | } 275 | } 276 | } 277 | pub type VkLayerDeviceLink = VkLayerDeviceLink_; 278 | #[repr(C)] 279 | pub struct VkLayerDeviceCreateInfo { 280 | pub sType: VkStructureType, 281 | pub pNext: *const ::std::os::raw::c_void, 282 | pub function: VkLayerFunction, 283 | pub u: VkLayerDeviceCreateInfo__bindgen_ty_1, 284 | } 285 | #[repr(C)] 286 | #[derive(Copy, Clone)] 287 | pub union VkLayerDeviceCreateInfo__bindgen_ty_1 { 288 | pub pLayerInfo: *mut VkLayerDeviceLink, 289 | pub pfnSetDeviceLoaderData: PFN_vkSetDeviceLoaderData, 290 | } 291 | #[test] 292 | fn bindgen_test_layout_VkLayerDeviceCreateInfo__bindgen_ty_1() { 293 | const UNINIT: ::std::mem::MaybeUninit = 294 | ::std::mem::MaybeUninit::uninit(); 295 | let ptr = UNINIT.as_ptr(); 296 | assert_eq!( 297 | ::std::mem::size_of::(), 298 | 8usize, 299 | concat!( 300 | "Size of: ", 301 | stringify!(VkLayerDeviceCreateInfo__bindgen_ty_1) 302 | ) 303 | ); 304 | assert_eq!( 305 | ::std::mem::align_of::(), 306 | 8usize, 307 | concat!( 308 | "Alignment of ", 309 | stringify!(VkLayerDeviceCreateInfo__bindgen_ty_1) 310 | ) 311 | ); 312 | assert_eq!( 313 | unsafe { ::std::ptr::addr_of!((*ptr).pLayerInfo) as usize - ptr as usize }, 314 | 0usize, 315 | concat!( 316 | "Offset of field: ", 317 | stringify!(VkLayerDeviceCreateInfo__bindgen_ty_1), 318 | "::", 319 | stringify!(pLayerInfo) 320 | ) 321 | ); 322 | assert_eq!( 323 | unsafe { ::std::ptr::addr_of!((*ptr).pfnSetDeviceLoaderData) as usize - ptr as usize }, 324 | 0usize, 325 | concat!( 326 | "Offset of field: ", 327 | stringify!(VkLayerDeviceCreateInfo__bindgen_ty_1), 328 | "::", 329 | stringify!(pfnSetDeviceLoaderData) 330 | ) 331 | ); 332 | } 333 | impl Default for VkLayerDeviceCreateInfo__bindgen_ty_1 { 334 | fn default() -> Self { 335 | let mut s = ::std::mem::MaybeUninit::::uninit(); 336 | unsafe { 337 | ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); 338 | s.assume_init() 339 | } 340 | } 341 | } 342 | #[test] 343 | fn bindgen_test_layout_VkLayerDeviceCreateInfo() { 344 | const UNINIT: ::std::mem::MaybeUninit = 345 | ::std::mem::MaybeUninit::uninit(); 346 | let ptr = UNINIT.as_ptr(); 347 | assert_eq!( 348 | ::std::mem::size_of::(), 349 | 32usize, 350 | concat!("Size of: ", stringify!(VkLayerDeviceCreateInfo)) 351 | ); 352 | assert_eq!( 353 | ::std::mem::align_of::(), 354 | 8usize, 355 | concat!("Alignment of ", stringify!(VkLayerDeviceCreateInfo)) 356 | ); 357 | assert_eq!( 358 | unsafe { ::std::ptr::addr_of!((*ptr).sType) as usize - ptr as usize }, 359 | 0usize, 360 | concat!( 361 | "Offset of field: ", 362 | stringify!(VkLayerDeviceCreateInfo), 363 | "::", 364 | stringify!(sType) 365 | ) 366 | ); 367 | assert_eq!( 368 | unsafe { ::std::ptr::addr_of!((*ptr).pNext) as usize - ptr as usize }, 369 | 8usize, 370 | concat!( 371 | "Offset of field: ", 372 | stringify!(VkLayerDeviceCreateInfo), 373 | "::", 374 | stringify!(pNext) 375 | ) 376 | ); 377 | assert_eq!( 378 | unsafe { ::std::ptr::addr_of!((*ptr).function) as usize - ptr as usize }, 379 | 16usize, 380 | concat!( 381 | "Offset of field: ", 382 | stringify!(VkLayerDeviceCreateInfo), 383 | "::", 384 | stringify!(function) 385 | ) 386 | ); 387 | assert_eq!( 388 | unsafe { ::std::ptr::addr_of!((*ptr).u) as usize - ptr as usize }, 389 | 24usize, 390 | concat!( 391 | "Offset of field: ", 392 | stringify!(VkLayerDeviceCreateInfo), 393 | "::", 394 | stringify!(u) 395 | ) 396 | ); 397 | } 398 | impl Default for VkLayerDeviceCreateInfo { 399 | fn default() -> Self { 400 | let mut s = ::std::mem::MaybeUninit::::uninit(); 401 | unsafe { 402 | ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); 403 | s.assume_init() 404 | } 405 | } 406 | } 407 | -------------------------------------------------------------------------------- /vulkan-layer/src/bindings/vk_layer/generated/unix.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* automatically generated by rust-bindgen 0.69.4 */ 16 | 17 | impl VkNegotiateLayerStructType { 18 | pub const LAYER_NEGOTIATE_UNINTIALIZED: VkNegotiateLayerStructType = 19 | VkNegotiateLayerStructType(0); 20 | } 21 | impl VkNegotiateLayerStructType { 22 | pub const LAYER_NEGOTIATE_INTERFACE_STRUCT: VkNegotiateLayerStructType = 23 | VkNegotiateLayerStructType(1); 24 | } 25 | #[repr(transparent)] 26 | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] 27 | pub struct VkNegotiateLayerStructType(pub ::std::os::raw::c_uint); 28 | impl VkLayerFunction_ { 29 | pub const VK_LAYER_LINK_INFO: VkLayerFunction_ = VkLayerFunction_(0); 30 | } 31 | impl VkLayerFunction_ { 32 | pub const VK_LOADER_DATA_CALLBACK: VkLayerFunction_ = VkLayerFunction_(1); 33 | } 34 | impl VkLayerFunction_ { 35 | pub const VK_LOADER_LAYER_CREATE_DEVICE_CALLBACK: VkLayerFunction_ = VkLayerFunction_(2); 36 | } 37 | impl VkLayerFunction_ { 38 | pub const VK_LOADER_FEATURES: VkLayerFunction_ = VkLayerFunction_(3); 39 | } 40 | #[repr(transparent)] 41 | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] 42 | pub struct VkLayerFunction_(pub ::std::os::raw::c_uint); 43 | pub use self::VkLayerFunction_ as VkLayerFunction; 44 | -------------------------------------------------------------------------------- /vulkan-layer/src/bindings/vk_layer/generated/windows.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* automatically generated by rust-bindgen 0.69.4 */ 16 | 17 | impl VkNegotiateLayerStructType { 18 | pub const LAYER_NEGOTIATE_UNINTIALIZED: VkNegotiateLayerStructType = 19 | VkNegotiateLayerStructType(0); 20 | } 21 | impl VkNegotiateLayerStructType { 22 | pub const LAYER_NEGOTIATE_INTERFACE_STRUCT: VkNegotiateLayerStructType = 23 | VkNegotiateLayerStructType(1); 24 | } 25 | #[repr(transparent)] 26 | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] 27 | pub struct VkNegotiateLayerStructType(pub ::std::os::raw::c_int); 28 | impl VkLayerFunction_ { 29 | pub const VK_LAYER_LINK_INFO: VkLayerFunction_ = VkLayerFunction_(0); 30 | } 31 | impl VkLayerFunction_ { 32 | pub const VK_LOADER_DATA_CALLBACK: VkLayerFunction_ = VkLayerFunction_(1); 33 | } 34 | impl VkLayerFunction_ { 35 | pub const VK_LOADER_LAYER_CREATE_DEVICE_CALLBACK: VkLayerFunction_ = VkLayerFunction_(2); 36 | } 37 | impl VkLayerFunction_ { 38 | pub const VK_LOADER_FEATURES: VkLayerFunction_ = VkLayerFunction_(3); 39 | } 40 | #[repr(transparent)] 41 | #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] 42 | pub struct VkLayerFunction_(pub ::std::os::raw::c_int); 43 | pub use self::VkLayerFunction_ as VkLayerFunction; 44 | -------------------------------------------------------------------------------- /vulkan-layer/src/global_simple_intercept.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::{ 16 | ffi::{c_void, CStr}, 17 | fmt::Debug, 18 | mem::MaybeUninit, 19 | }; 20 | 21 | use ash::vk; 22 | 23 | use smallvec::SmallVec; 24 | use thiserror::Error; 25 | 26 | pub mod generated; 27 | pub use generated::*; 28 | 29 | use crate::vk_utils::ptr_as_uninit_mut; 30 | 31 | #[derive(Error, Debug)] 32 | pub enum TryFromExtensionError { 33 | #[error("unknown extension `{0}`")] 34 | UnknownExtension(String), 35 | } 36 | 37 | /// A union type of extensions and core API version. 38 | /// 39 | /// In `vk.xml`, Vulkan commands and types are grouped under different API version and extensions. 40 | /// The tag of those group XML elements is `feature` or `extension`. One command will have only one 41 | /// single correspondent feature. This type is mostly used to tell if a command should be returned 42 | /// by `vkGet*ProcAddr` given the supported/enabled Vulkan API version and extensions. 43 | #[derive(PartialEq, Eq, PartialOrd, Ord, Clone)] 44 | pub enum Feature { 45 | /// Vulkan core API interface. 46 | Core(ApiVersion), 47 | /// Vulkan extension interface. 48 | Extension(Extension), 49 | } 50 | 51 | impl From for Feature { 52 | fn from(value: ApiVersion) -> Self { 53 | Self::Core(value) 54 | } 55 | } 56 | 57 | impl From for Feature { 58 | fn from(value: Extension) -> Self { 59 | Self::Extension(value) 60 | } 61 | } 62 | 63 | /// Vulkan API version number. 64 | /// 65 | /// Can be used to store the result decoded from 66 | /// [`VK_MAKE_API_VERSION`](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_MAKE_API_VERSION.html). 67 | #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] 68 | pub struct ApiVersion { 69 | /// The major version number. At most 7 bits. 70 | pub major: u8, 71 | /// The minor version number. At most 10 bits 72 | pub minor: u16, 73 | } 74 | 75 | impl ApiVersion { 76 | /// Vulkan version 1.0. The initial release of the Vulkan API. 77 | /// 78 | /// 79 | pub const V1_0: Self = Self { major: 1, minor: 0 }; 80 | /// Vulkan version 1.1. 81 | /// 82 | /// 83 | pub const V1_1: Self = Self { major: 1, minor: 1 }; 84 | } 85 | 86 | impl From for ApiVersion { 87 | fn from(value: u32) -> Self { 88 | Self { 89 | major: vk::api_version_major(value) 90 | .try_into() 91 | .expect("The major version must be no more than 7 bits."), 92 | minor: vk::api_version_minor(value) 93 | .try_into() 94 | .expect("The minor version must be no more than 10 bits."), 95 | } 96 | } 97 | } 98 | 99 | impl From for u32 { 100 | fn from(value: ApiVersion) -> Self { 101 | vk::make_api_version(0, value.major.into(), value.minor.into(), 0) 102 | } 103 | } 104 | 105 | pub(crate) struct VulkanCommand { 106 | pub name: &'static str, 107 | pub features: SmallVec<[Feature; 2]>, 108 | pub hooked: bool, 109 | pub proc: vk::PFN_vkVoidFunction, 110 | } 111 | 112 | fn get_instance_proc_addr_loader( 113 | get_instance_proc_addr: vk::PFN_vkGetInstanceProcAddr, 114 | instance: &ash::Instance, 115 | ) -> impl Fn(&CStr) -> *const c_void + '_ { 116 | move |name| { 117 | // Safe because the VkInstance is valid, and a valid C string pointer is passed to the 118 | // `p_name` parameter. 119 | let fp = unsafe { get_instance_proc_addr(instance.handle(), name.as_ptr()) }; 120 | if let Some(fp) = fp { 121 | return fp as *const _; 122 | } 123 | std::ptr::null() 124 | } 125 | } 126 | 127 | fn get_device_proc_addr_loader( 128 | get_device_proc_addr: vk::PFN_vkGetDeviceProcAddr, 129 | device: &ash::Device, 130 | ) -> impl Fn(&CStr) -> *const c_void + '_ { 131 | move |name| { 132 | // Safe because the VkDevice is valid, and a valid C string pointer is passed to the 133 | // `p_name` parameter. 134 | let fp = unsafe { get_device_proc_addr(device.handle(), name.as_ptr()) }; 135 | match fp { 136 | Some(fp) => fp as *const _, 137 | None => std::ptr::null(), 138 | } 139 | } 140 | } 141 | 142 | /// Converts a raw pointer to a reference of a slice of maybe-uninit. In contrast to 143 | /// `from_raw_parts_mut`, this does not require that the value has to be initialized and the input 144 | /// pointer can be null and may not be aligned if the input size is 0. If either `p_out_array` or 145 | /// `p_size` is null, [`None`] is returned. Otherwise, [`Some`] is returned. 146 | /// 147 | /// # Safety 148 | /// Behavior is undefined if any of the following conditions are violated: 149 | /// 150 | /// * `p_data` must be valid for writes for `size * mem::size_of::()` many bytes, and it must be 151 | /// properly aligned. This means in particular, the entire memory range of this slice must be 152 | /// contained within a single allocated object! Slices can never span across multiple allocated 153 | /// objects. `p_data` must be be aligned if `size` is not 0. 154 | /// * The memory referenced by the returned slice must not be accessed through any other pointer 155 | /// (not derived from the return value) for the duration of lifetime 'a. Both read and write 156 | /// accesses are forbidden. 157 | /// * The total size `size * mem::size_of::()` of the slice must be no larger than 158 | /// [`std::isize::MAX`], and adding that size to data must not “wrap around” the address space. 159 | /// See the safety documentation of 160 | /// [`pointer::offset`](https://doc.rust-lang.org/std/primitive.pointer.html#method.offset). 161 | /// * `p_size` must be either null or points to a valid data to read. See details at [`pointer::as_ref`](https://doc.rust-lang.org/std/primitive.pointer.html#safety). 162 | /// 163 | /// # Panics 164 | /// * Panics if `size` can't be converted to usize. 165 | #[deny(unsafe_op_in_unsafe_fn)] 166 | unsafe fn maybe_uninit_slice_from_raw_parts_mut<'a, T>( 167 | p_out_array: *mut T, 168 | p_size: *const (impl TryInto + Copy), 169 | ) -> Option<&'a mut [MaybeUninit]> { 170 | let size: usize = unsafe { p_size.as_ref() } 171 | .copied()? 172 | .try_into() 173 | .expect("size mut be within the range of usize"); 174 | if p_out_array.is_null() { 175 | return None; 176 | } 177 | Some(unsafe { uninit_slice_from_raw_parts_mut(p_out_array, size) }) 178 | } 179 | 180 | /// Forms a slice from a pointer and a length. 181 | /// 182 | /// In contrast to [`std::slice::from_raw_parts`], this does not require that `data` must be 183 | /// non-null and unaligned for zero-length slice. If `data` is null, [`None`] is returned. 184 | /// 185 | /// # Safety 186 | /// 187 | /// If `len` is 0, there is no safety requirement. 188 | /// 189 | /// If `len` is not 0, the following conditions shouldn't be violated: 190 | /// * `data` must be valid for reads for `len * mem::size_of::()` many bytes, and it must be 191 | /// properly aligned. This means in particular: the entire memory range of this slice must be 192 | /// contained within a single allocated object! Slices can never span across multiple allocated 193 | /// objects. 194 | /// * `data` must point to `len` consecutive properly initialized values of type `T`. 195 | /// * The memory referenced by the returned slice must not be mutated for the duration of lifetime 196 | /// `'a`, except inside an `UnsafeCell`. 197 | /// * The total size `len * mem::size_of::()` of the slice must be no larger than [`isize::MAX`], 198 | /// and adding that size to data must not “wrap around” the address space. See the safety 199 | /// documentation of 200 | /// [`pointer::offset`](https://doc.rust-lang.org/std/primitive.pointer.html#method.offset). 201 | /// 202 | /// # Panics 203 | /// 204 | /// Panics if `len` can't be converted to `uszie`. 205 | #[deny(unsafe_op_in_unsafe_fn)] 206 | unsafe fn maybe_slice_from_raw_parts<'a, T>( 207 | data: *const T, 208 | len: impl TryInto, 209 | ) -> Option<&'a [T]> { 210 | if data.is_null() { 211 | return None; 212 | } 213 | let len: usize = len 214 | .try_into() 215 | .expect("len mut be within the range of usize"); 216 | if len == 0 { 217 | return Some(&[]); 218 | } 219 | Some(unsafe { std::slice::from_raw_parts(data, len) }) 220 | } 221 | 222 | /// Converts a raw pointer to a reference of a slice of maybe-uninit. In contrast to 223 | /// `from_raw_parts_mut`, this does not require that the value has to be initialized and the input 224 | /// pointer can be null and may not be aligned if the input size is 0. 225 | /// 226 | /// # Safety 227 | /// Behavior is undefined if any of the following conditions are violated: 228 | /// 229 | /// * `p_data` must be valid for writes for `size * mem::size_of::()` many bytes, and it must be 230 | /// properly aligned. This means in particular, the entire memory range of this slice must be 231 | /// contained within a single allocated object! Slices can never span across multiple allocated 232 | /// objects. `p_data` must be be aligned if `size` is not 0. 233 | /// * The memory referenced by the returned slice must not be accessed through any other pointer 234 | /// (not derived from the return value) for the duration of lifetime 'a. Both read and write 235 | /// accesses are forbidden. 236 | /// * The total size `size * mem::size_of::()` of the slice must be no larger than 237 | /// [`std::isize::MAX`], and adding that size to data must not “wrap around” the address space. 238 | /// See the safety documentation of 239 | /// [`pointer::offset`](https://doc.rust-lang.org/std/primitive.pointer.html#method.offset). 240 | /// 241 | /// # Panics 242 | /// * Panics if `size` is not 0 and `p_data` is null. 243 | /// * Panics if `size` can't be converted to usize. 244 | #[deny(unsafe_op_in_unsafe_fn)] 245 | unsafe fn uninit_slice_from_raw_parts_mut<'a, T>( 246 | p_data: *mut T, 247 | size: impl TryInto, 248 | ) -> &'a mut [MaybeUninit] { 249 | let size: usize = size 250 | .try_into() 251 | .expect("size mut be within the range of usize"); 252 | if size == 0 { 253 | return &mut []; 254 | } 255 | let first_element = 256 | unsafe { ptr_as_uninit_mut(p_data) }.expect("the input data pointer should not be null"); 257 | unsafe { std::slice::from_raw_parts_mut(first_element, size) } 258 | } 259 | 260 | #[deny(unsafe_op_in_unsafe_fn)] 261 | unsafe fn bool_iterator_from_raw_parts( 262 | ptr: *const vk::Bool32, 263 | size: impl TryInto, 264 | ) -> impl Iterator { 265 | let size: usize = size 266 | .try_into() 267 | .expect("size mut be within the range of usize"); 268 | let slice = if size == 0 { 269 | &[] 270 | } else { 271 | unsafe { std::slice::from_raw_parts(ptr, size) } 272 | }; 273 | slice.iter().map(|v| *v == vk::TRUE) 274 | } 275 | 276 | #[cfg(test)] 277 | mod tests { 278 | use super::*; 279 | 280 | #[test] 281 | fn bool_iterator_from_raw_parts_result_should_match() { 282 | let expected_value = vec![true, true, false, true, false]; 283 | let input = expected_value 284 | .iter() 285 | .map(|v| if *v { vk::TRUE } else { vk::FALSE }) 286 | .collect::>(); 287 | let result = unsafe { bool_iterator_from_raw_parts(input.as_ptr(), expected_value.len()) } 288 | .collect::>(); 289 | assert_eq!(result, expected_value); 290 | } 291 | 292 | #[test] 293 | fn bool_iterator_from_raw_parts_empty_with_null_ptr() { 294 | let result = 295 | unsafe { bool_iterator_from_raw_parts(std::ptr::null(), 0) }.collect::>(); 296 | assert!(result.is_empty()); 297 | } 298 | 299 | #[test] 300 | fn maybe_uninit_slice_from_raw_parts_mut_result_should_match() { 301 | const LEN: usize = 10; 302 | let expected_value: [i32; LEN] = [81, 95, 43, 65, 34, 47, 65, 62, 47, 82]; 303 | let mut input = [MaybeUninit::::uninit(); LEN]; 304 | let input_ptr = input.as_mut_ptr() as *mut i32; 305 | let output = unsafe { maybe_uninit_slice_from_raw_parts_mut(input_ptr, &LEN) } 306 | .expect("for valid input should return in Some"); 307 | assert_eq!(output.len(), LEN); 308 | for (i, output_element) in output.iter_mut().enumerate() { 309 | output_element.write(expected_value[i]); 310 | } 311 | 312 | for (i, input_element) in input.iter().enumerate() { 313 | assert_eq!( 314 | *unsafe { input_element.assume_init_ref() }, 315 | expected_value[i] 316 | ); 317 | } 318 | } 319 | 320 | #[test] 321 | fn maybe_uninit_slice_from_raw_parts_mut_null_data_ptr() { 322 | let input_ptr: *mut i32 = std::ptr::null_mut(); 323 | let output = unsafe { maybe_uninit_slice_from_raw_parts_mut(input_ptr, &10) }; 324 | assert!(output.is_none()); 325 | } 326 | 327 | #[test] 328 | fn maybe_uninit_slice_from_raw_parts_zero_length_unaligned_data_ptr() { 329 | // Some address in the u8_array must be unaligned with u32. 330 | let mut u8_array = [0u8; 2]; 331 | let input_ptrs = [ 332 | &mut u8_array[0] as *mut _ as *mut i32, 333 | &mut u8_array[1] as *mut _ as *mut i32, 334 | ]; 335 | for input_ptr in input_ptrs { 336 | let output = unsafe { maybe_uninit_slice_from_raw_parts_mut(input_ptr, &0) } 337 | .expect("for valid input pointer, Some should be returned"); 338 | assert!(output.is_empty()); 339 | } 340 | } 341 | 342 | #[test] 343 | fn maybe_uninit_slice_from_raw_parts_mut_null_size_ptr() { 344 | let mut data = MaybeUninit::::uninit(); 345 | let output = unsafe { 346 | maybe_uninit_slice_from_raw_parts_mut(data.as_mut_ptr(), std::ptr::null::()) 347 | }; 348 | assert!(output.is_none()); 349 | } 350 | 351 | #[test] 352 | #[should_panic] 353 | fn maybe_uninit_slice_from_raw_parts_mut_invalid_size_value() { 354 | let mut data = MaybeUninit::::uninit(); 355 | unsafe { maybe_uninit_slice_from_raw_parts_mut(data.as_mut_ptr(), &-1) }; 356 | } 357 | 358 | #[test] 359 | fn uninit_slice_from_raw_parts_mut_zero_size_and_invalid_data_address() { 360 | // Some address in the u8_array must be unaligned with u32. 361 | let mut u8_array = [0u8; 2]; 362 | let input_ptrs = [ 363 | &mut u8_array[0] as *mut _ as *mut i32, 364 | &mut u8_array[1] as *mut _ as *mut i32, 365 | ]; 366 | for input_ptr in input_ptrs { 367 | let output = unsafe { uninit_slice_from_raw_parts_mut(input_ptr, 0) }; 368 | assert!(output.is_empty()); 369 | } 370 | 371 | let output = unsafe { uninit_slice_from_raw_parts_mut(std::ptr::null_mut::(), 0) }; 372 | assert!(output.is_empty()); 373 | } 374 | 375 | #[test] 376 | #[should_panic] 377 | fn uninit_slice_from_raw_parts_mut_valid_size_and_null_data_address() { 378 | unsafe { uninit_slice_from_raw_parts_mut(std::ptr::null_mut::(), 10) }; 379 | } 380 | 381 | #[test] 382 | #[should_panic] 383 | fn uninit_slice_from_raw_parts_mut_invalid_size_value() { 384 | let mut data = MaybeUninit::::uninit(); 385 | unsafe { uninit_slice_from_raw_parts_mut(data.as_mut_ptr(), -1) }; 386 | } 387 | 388 | #[test] 389 | fn uninit_slice_from_raw_parts_mut_result_should_match() { 390 | const LEN: usize = 10; 391 | let expected_value: [i32; LEN] = [14, 45, 60, 97, 35, 21, 13, 42, 11, 12]; 392 | let mut input = [MaybeUninit::::uninit(); LEN]; 393 | let input_ptr = input.as_mut_ptr() as *mut i32; 394 | let output = unsafe { uninit_slice_from_raw_parts_mut(input_ptr, LEN) }; 395 | assert_eq!(output.len(), LEN); 396 | for (i, output_element) in output.iter_mut().enumerate() { 397 | output_element.write(expected_value[i]); 398 | } 399 | 400 | for (i, input_element) in input.iter().enumerate() { 401 | assert_eq!( 402 | *unsafe { input_element.assume_init_ref() }, 403 | expected_value[i] 404 | ); 405 | } 406 | } 407 | 408 | #[test] 409 | fn maybe_slice_from_raw_parts_null_data() { 410 | let res = unsafe { maybe_slice_from_raw_parts(std::ptr::null::(), 0) }; 411 | assert!(res.is_none()); 412 | let res = unsafe { maybe_slice_from_raw_parts(std::ptr::null::(), 10) }; 413 | assert!(res.is_none()); 414 | } 415 | 416 | #[test] 417 | fn maybe_slice_from_raw_parts_zero_length_invalid_data_ptr() { 418 | let u8_array = [0u8; 2]; 419 | // Some address must not be aligned. 420 | let input_ptrs = u8_array 421 | .iter() 422 | .map(|element| element as *const _ as *const u32) 423 | .collect::>(); 424 | for input_ptr in input_ptrs { 425 | let res = unsafe { maybe_slice_from_raw_parts(input_ptr, 0) } 426 | .expect("should always return Some for non-null pointers"); 427 | assert!(res.is_empty()); 428 | } 429 | } 430 | 431 | #[test] 432 | fn maybe_slice_from_raw_parts_reflects_original_array() { 433 | let input: [u32; 10] = [47, 63, 14, 13, 8, 45, 52, 97, 21, 10]; 434 | let result = unsafe { maybe_slice_from_raw_parts(input.as_ptr(), input.len()) } 435 | .expect("should always return Some for non-null pointers"); 436 | assert_eq!(result, &input); 437 | } 438 | 439 | #[test] 440 | #[should_panic] 441 | fn maybe_slice_from_raw_parts_bad_len() { 442 | unsafe { maybe_slice_from_raw_parts(std::ptr::NonNull::::dangling().as_ptr(), -1) }; 443 | } 444 | 445 | #[test] 446 | fn extension_try_from_should_return_error_on_unknown_extension() { 447 | let unknown_extension = "VK_UNKNOWN_unknown"; 448 | let err = Extension::try_from(unknown_extension).unwrap_err(); 449 | assert!(err.to_string().contains(unknown_extension)); 450 | } 451 | } 452 | -------------------------------------------------------------------------------- /vulkan-layer/src/lazy_collection.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::{ 16 | borrow::Cow, 17 | cell::RefCell, 18 | collections::BTreeMap, 19 | marker::PhantomData, 20 | ops::{Deref, DerefMut}, 21 | }; 22 | 23 | use once_cell::unsync::OnceCell; 24 | 25 | /// The [CheckEmpty] trait allows to check if a collection is empty. 26 | pub trait CheckEmpty: Default + Clone { 27 | /// Returns `true` if the collection contains no elements. 28 | fn is_empty(&self) -> bool; 29 | } 30 | 31 | impl CheckEmpty for Vec { 32 | fn is_empty(&self) -> bool { 33 | Vec::::is_empty(self) 34 | } 35 | } 36 | 37 | impl CheckEmpty for BTreeMap { 38 | fn is_empty(&self) -> bool { 39 | BTreeMap::::is_empty(self) 40 | } 41 | } 42 | 43 | /// A collection wrapper, that guarantees that an empty collection can be trivially destructed. 44 | /// 45 | /// This makes it easy to use collections in a global object that requires trivially destructible. 46 | /// When using global objects in a dynamic link library that allow a process to load and unload the 47 | /// dynamic link library multiple times, no OS provides reliable way to teardown the global 48 | /// resources allocated, so all global resources used should be trivially destructible. 49 | /// 50 | /// This is especially true for an Android Vulkan layer. When querying the capabilities of Vulkan 51 | /// layers, the Android Vulkan loader will load the shared object and call into the exposed 52 | /// introspection queries, and unload the shared object once the task is done. The Android Vulkan 53 | /// loader may load the shared object later again if the layer is activated. However, on Android 54 | /// there is no reliable way to register a callback when the shared object is actually unloaded. 55 | /// 56 | /// Similar to [RefCell], even if `T` implements [Sync], [`LazyCollection`] is not [Sync], 57 | /// because a single-threaded way is used to test if the underlying collection is empty and destroy 58 | /// the allocation 59 | #[derive(Default)] 60 | pub struct LazyCollection { 61 | inner: OnceCell, 62 | // Mark this type !Sync even if T implements Sync. 63 | marker: PhantomData>, 64 | } 65 | 66 | impl LazyCollection { 67 | /// Creates a new `LazyCollection` containing `value`. 68 | /// 69 | /// # Examples 70 | /// ``` 71 | /// # use vulkan_layer::unstable_api::LazyCollection; 72 | /// let c = LazyCollection::new(vec![42]); 73 | /// ``` 74 | #[allow(dead_code)] 75 | // Only test uses this function. 76 | pub fn new(value: T) -> Self { 77 | Self { 78 | inner: OnceCell::with_value(value), 79 | ..Default::default() 80 | } 81 | } 82 | 83 | /// Gets the reference to the underlying collection. Returns an owned empty T if the underlying 84 | /// collection is empty. 85 | /// 86 | /// # Examples 87 | /// ``` 88 | /// # use vulkan_layer::unstable_api::LazyCollection; 89 | /// let vec = LazyCollection::new(vec![42]); 90 | /// let vec1 = vec.get(); 91 | /// assert_eq!(*vec1, vec![42]); 92 | /// let vec2 = vec.get(); 93 | /// // vec1 and vec2 point to the same location. 94 | /// assert!(std::ptr::eq(&*vec1, &*vec2)); 95 | /// ``` 96 | pub fn get(&self) -> Cow { 97 | // The destructor for None is a no-op, while this is not guaranteed for an empty T. 98 | // Therefore, we can't use &T as the return type and return a reference to a static empty T 99 | // when the underlying collection is empty. 100 | match self.inner.get() { 101 | Some(collection) => Cow::Borrowed(collection), 102 | None => Cow::Owned(Default::default()), 103 | } 104 | } 105 | 106 | /// Gets a mutable reference to the underlying collection, create an empty collection if the 107 | /// underlying collection was empty. 108 | /// 109 | /// # Examples 110 | /// ``` 111 | /// # use vulkan_layer::unstable_api::LazyCollection; 112 | /// let mut vec = LazyCollection::>::default(); 113 | /// let mut mut_vec = vec.get_mut_or_default(); 114 | /// mut_vec.push(42); 115 | /// assert_eq!(*mut_vec, vec![42]); 116 | /// drop(mut_vec); 117 | /// 118 | /// let mut mut_vec = vec.get_mut_or_default(); 119 | /// mut_vec.remove(0); 120 | /// assert_eq!(*mut_vec, vec![]); 121 | /// drop(mut_vec); 122 | /// // This won't cause a memory leak. 123 | /// std::mem::forget(vec); 124 | /// ``` 125 | pub fn get_mut_or_default(&mut self) -> CollectionRefMut<'_, T> { 126 | // Ensure that inner is initialized. 127 | self.inner.get_or_init(Default::default); 128 | CollectionRefMut(&mut self.inner) 129 | } 130 | } 131 | 132 | /// A wrapper type for a mutably borrowed value from a [LazyCollection]. 133 | #[derive(Debug)] 134 | pub struct CollectionRefMut<'a, T: CheckEmpty>(&'a mut OnceCell); 135 | 136 | impl Drop for CollectionRefMut<'_, T> { 137 | fn drop(&mut self) { 138 | let should_destroy = self 139 | .0 140 | .get() 141 | .map(|collection| collection.is_empty()) 142 | .unwrap_or(true); 143 | if !should_destroy { 144 | return; 145 | } 146 | self.0.take(); 147 | } 148 | } 149 | 150 | impl Deref for CollectionRefMut<'_, T> { 151 | type Target = T; 152 | fn deref(&self) -> &Self::Target { 153 | // CollectionRefMut will always be initialized. get_mut_or_default is the only place we 154 | // initialize CollectionRefMut, and we never mutate it. 155 | self.0.get().unwrap() 156 | } 157 | } 158 | 159 | impl DerefMut for CollectionRefMut<'_, T> { 160 | fn deref_mut(&mut self) -> &mut Self::Target { 161 | // CollectionRefMut will always be initialized. get_mut_or_default is the only place we 162 | // initialize CollectionRefMut, and we never mutate it. 163 | self.0.get_mut().unwrap() 164 | } 165 | } 166 | 167 | #[cfg(test)] 168 | mod tests { 169 | use super::*; 170 | 171 | #[test] 172 | fn test_empty_get_shouldnt_leak() { 173 | // We rely on the Miri test to detect the resource leak. 174 | let lazy_vec = LazyCollection::>::new(vec![]); 175 | let empty_vec = lazy_vec.get(); 176 | assert!(empty_vec.is_empty()); 177 | std::mem::forget(lazy_vec); 178 | 179 | let lazy_vec = LazyCollection::>::default(); 180 | let empty_vec = lazy_vec.get(); 181 | assert!(empty_vec.is_empty()); 182 | std::mem::forget(lazy_vec); 183 | } 184 | 185 | #[test] 186 | fn test_non_empty_get_should_point_to_the_same_location() { 187 | let lazy_vec = LazyCollection::new(vec![42]); 188 | let vec1 = lazy_vec.get(); 189 | assert_eq!(*vec1, vec![42]); 190 | let vec2 = lazy_vec.get(); 191 | assert!(std::ptr::eq(&*vec1, &*vec2)); 192 | } 193 | 194 | #[test] 195 | fn test_get_mut_should_get_the_content() { 196 | let mut lazy_vec = LazyCollection::new(vec![64]); 197 | { 198 | let mut vec = lazy_vec.get_mut_or_default(); 199 | assert_eq!(*vec, vec![64]); 200 | vec.push(33); 201 | assert_eq!(*vec, vec![64, 33]); 202 | } 203 | let vec = lazy_vec.get_mut_or_default(); 204 | assert_eq!(*vec, vec![64, 33]); 205 | } 206 | 207 | #[test] 208 | fn test_get_mut_empty_should_return_an_empty_collection() { 209 | let mut lazy_vec = LazyCollection::>::default(); 210 | assert!(lazy_vec.get_mut_or_default().is_empty()); 211 | } 212 | 213 | #[test] 214 | fn test_get_mut_empty_shouldnt_leak() { 215 | // We rely on the Miri test to detect the resource leak. 216 | let mut lazy_vec = LazyCollection::>::default(); 217 | { 218 | let vec = lazy_vec.get_mut_or_default(); 219 | assert!(vec.is_empty()); 220 | } 221 | std::mem::forget(lazy_vec); 222 | } 223 | 224 | #[test] 225 | fn test_get_mut_insert_and_clear_shouldnt_leak() { 226 | // We rely on the Miri test to detect the resource leak. 227 | // 2 test cases. One on the same CollectionRefMut. One on 2 different CollectionRefMut's. 228 | { 229 | let mut lazy_vec = LazyCollection::>::default(); 230 | let mut vec = lazy_vec.get_mut_or_default(); 231 | vec.push(42); 232 | assert!(!vec.is_empty()); 233 | vec.remove(0); 234 | assert!(vec.is_empty()); 235 | drop(vec); 236 | std::mem::forget(lazy_vec); 237 | } 238 | { 239 | let mut lazy_vec = LazyCollection::>::default(); 240 | let mut vec = lazy_vec.get_mut_or_default(); 241 | vec.push(42); 242 | assert!(!vec.is_empty()); 243 | drop(vec); 244 | let mut vec = lazy_vec.get_mut_or_default(); 245 | vec.remove(0); 246 | assert!(vec.is_empty()); 247 | drop(vec); 248 | std::mem::forget(lazy_vec); 249 | } 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /vulkan-layer/src/test_utils/device_hooks_mock.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use mockall::mock; 16 | 17 | use crate::{DeviceHooks, LayerResult}; 18 | use ash::vk; 19 | 20 | mock! { 21 | pub DeviceHooks {} 22 | impl DeviceHooks for DeviceHooks { 23 | fn destroy_image<'a>( 24 | &self, 25 | _image: vk::Image, 26 | _p_allocator: Option<&'a vk::AllocationCallbacks>, 27 | ) -> LayerResult<()>; 28 | 29 | fn destroy_sampler_ycbcr_conversion<'a>( 30 | &self, 31 | _ycbcr_conversion: vk::SamplerYcbcrConversion, 32 | _p_allocator: Option<&'a vk::AllocationCallbacks>, 33 | ) -> LayerResult<()>; 34 | 35 | fn destroy_swapchain_khr<'a>( 36 | &self, 37 | _swapchain: vk::SwapchainKHR, 38 | _p_allocator: Option<&'a vk::AllocationCallbacks>, 39 | ) -> LayerResult<()>; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /vulkan-layer/src/test_utils/global_hooks_mock.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use mockall::mock; 16 | 17 | use crate::{GlobalHooks, LayerResult, VkLayerInstanceLink}; 18 | use ash::{prelude::VkResult, vk}; 19 | 20 | mock! { 21 | pub GlobalHooks {} 22 | impl GlobalHooks for GlobalHooks { 23 | fn create_instance<'a>( 24 | &self, 25 | _p_create_info: &vk::InstanceCreateInfo, 26 | _layer_instance_link: &VkLayerInstanceLink, 27 | _p_allocator: Option<&'a vk::AllocationCallbacks>, 28 | _p_instance: *mut vk::Instance, 29 | ) -> LayerResult>; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /vulkan-layer/src/test_utils/instance_hooks_mock.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::mem::MaybeUninit; 16 | 17 | use mockall::mock; 18 | 19 | use crate::{InstanceHooks, LayerResult, VkLayerDeviceLink}; 20 | use ash::{prelude::VkResult, vk}; 21 | 22 | // We don't automock the original trait, because that hurts compilation speed significantly. 23 | mock! { 24 | pub InstanceHooks {} 25 | impl InstanceHooks for InstanceHooks { 26 | fn destroy_surface_khr<'a>( 27 | &self, 28 | surface: vk::SurfaceKHR, 29 | p_allocator: Option<&'a vk::AllocationCallbacks>, 30 | ) -> LayerResult<()>; 31 | 32 | fn create_device<'a>( 33 | &self, 34 | _physical_device: vk::PhysicalDevice, 35 | _p_create_info: &vk::DeviceCreateInfo, 36 | _layer_device_link: &VkLayerDeviceLink, 37 | _p_allocator: Option<&'a vk::AllocationCallbacks>, 38 | _p_device: &mut MaybeUninit, 39 | ) -> LayerResult>; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /vulkan-layer/src/unstable_api.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //! Vulkan layer utilities that are used in the integration tests to implement mock layers. The 16 | //! utilities may benefit the layer implementation, but there is no stability guarantee on those 17 | //! APIs. 18 | 19 | pub use crate::{ 20 | global_simple_intercept::{ApiVersion, Feature}, 21 | lazy_collection::{CheckEmpty, LazyCollection}, 22 | vk_utils::IsCommandEnabled, 23 | }; 24 | -------------------------------------------------------------------------------- /vulkan-layer/tests/autoinfo_macros_test.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use ash::vk; 16 | use mockall::automock; 17 | use once_cell::sync::Lazy; 18 | use std::{marker::PhantomData, sync::Arc}; 19 | use vulkan_layer::{ 20 | auto_deviceinfo_impl, auto_globalhooksinfo_impl, auto_instanceinfo_impl, 21 | test_utils::LayerManifestExt, DeviceHooks, DeviceInfo, Global, GlobalHooks, GlobalHooksInfo, 22 | InstanceHooks, InstanceInfo, Layer, LayerManifest, LayerResult, LayerVulkanCommand, 23 | StubDeviceInfo, StubGlobalHooks, StubInstanceInfo, 24 | }; 25 | 26 | #[automock] 27 | pub trait GlobalInstanceProvider { 28 | fn instance() -> &'static Global; 29 | } 30 | 31 | #[derive(Default)] 32 | struct TestLayer 33 | where 34 | T: GlobalHooksInfo + Default + 'static, 35 | U: InstanceInfo + Default + 'static, 36 | V: DeviceInfo + Default + 'static, 37 | { 38 | global_hooks: T, 39 | _marker: PhantomData, 40 | } 41 | 42 | impl Layer for TestLayer 43 | where 44 | T: GlobalHooksInfo + Default + 'static, 45 | U: InstanceInfo + Default + 'static, 46 | V: DeviceInfo + Default + 'static, 47 | { 48 | type GlobalHooksInfo = T; 49 | type InstanceInfo = U; 50 | type DeviceInfo = V; 51 | type InstanceInfoContainer = U; 52 | type DeviceInfoContainer = V; 53 | 54 | fn global_instance() -> impl std::ops::Deref> + 'static { 55 | MockGlobalInstanceProvider::::instance() 56 | } 57 | 58 | fn manifest() -> LayerManifest { 59 | LayerManifest::test_default() 60 | } 61 | 62 | fn global_hooks_info(&self) -> &Self::GlobalHooksInfo { 63 | &self.global_hooks 64 | } 65 | 66 | fn create_device_info( 67 | &self, 68 | _: vk::PhysicalDevice, 69 | _: &vk::DeviceCreateInfo, 70 | _: Option<&vk::AllocationCallbacks>, 71 | _: Arc, 72 | _next_get_device_proc_addr: vk::PFN_vkGetDeviceProcAddr, 73 | ) -> Self::DeviceInfoContainer { 74 | Default::default() 75 | } 76 | 77 | fn create_instance_info( 78 | &self, 79 | _: &vk::InstanceCreateInfo, 80 | _: Option<&vk::AllocationCallbacks>, 81 | _: Arc, 82 | _next_get_instance_proc_addr: vk::PFN_vkGetInstanceProcAddr, 83 | ) -> Self::InstanceInfoContainer { 84 | Default::default() 85 | } 86 | } 87 | 88 | #[test] 89 | fn test_auto_globalhooksinfo_should_intercept_hooked_proc() { 90 | type MockLayer = TestLayer; 91 | static GLOBAL: Lazy> = Lazy::new(Default::default); 92 | let ctx = MockGlobalInstanceProvider::instance_context(); 93 | ctx.expect().return_const(&*GLOBAL); 94 | #[derive(Default)] 95 | struct TestGlobalHooks; 96 | #[auto_globalhooksinfo_impl] 97 | impl GlobalHooks for TestGlobalHooks { 98 | fn create_instance( 99 | &self, 100 | _p_create_info: &vk::InstanceCreateInfo, 101 | _layer_instance_link: &vulkan_layer::VkLayerInstanceLink, 102 | _p_allocator: Option<&vk::AllocationCallbacks>, 103 | _: *mut vk::Instance, 104 | ) -> LayerResult> { 105 | unimplemented!() 106 | } 107 | } 108 | let hooked_commands = ::GlobalHooksInfo::hooked_commands().to_vec(); 109 | assert_eq!(hooked_commands, &[LayerVulkanCommand::CreateInstance]); 110 | } 111 | 112 | #[test] 113 | fn test_auto_instanceinfo_should_intercept_hooked_proc() { 114 | type MockLayer = TestLayer; 115 | static GLOBAL: Lazy> = Lazy::new(Default::default); 116 | let ctx = MockGlobalInstanceProvider::instance_context(); 117 | ctx.expect().return_const(&*GLOBAL); 118 | #[derive(Default)] 119 | struct TestInstanceInfo; 120 | #[auto_instanceinfo_impl] 121 | impl InstanceHooks for TestInstanceInfo { 122 | fn destroy_surface_khr( 123 | &self, 124 | _: vk::SurfaceKHR, 125 | _: Option<&vk::AllocationCallbacks>, 126 | ) -> LayerResult<()> { 127 | unimplemented!() 128 | } 129 | } 130 | let hooked_commands = MockLayer::global_instance() 131 | .layer_info 132 | .hooked_instance_commands(&Default::default()) 133 | .collect::>(); 134 | assert_eq!(hooked_commands, &[LayerVulkanCommand::DestroySurfaceKhr]); 135 | } 136 | 137 | #[test] 138 | fn test_auto_deviceinfo_should_intercept_hooked_proc() { 139 | type MockLayer = TestLayer; 140 | static GLOBAL: Lazy> = Lazy::new(Default::default); 141 | let ctx = MockGlobalInstanceProvider::instance_context(); 142 | ctx.expect().return_const(&*GLOBAL); 143 | #[derive(Default)] 144 | struct TestDeviceInfo; 145 | #[auto_deviceinfo_impl] 146 | impl DeviceHooks for TestDeviceInfo { 147 | fn destroy_image( 148 | &self, 149 | _image: vk::Image, 150 | _p_allocator: Option<&vk::AllocationCallbacks>, 151 | ) -> LayerResult<()> { 152 | unimplemented!() 153 | } 154 | } 155 | let hooked_commands = MockLayer::global_instance() 156 | .layer_info 157 | .hooked_device_commands(&Default::default(), None) 158 | .collect::>(); 159 | assert_eq!(hooked_commands, &[LayerVulkanCommand::DestroyImage]); 160 | } 161 | -------------------------------------------------------------------------------- /vulkan-layer/tests/declare_introspection_queries_test.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | use std::ffi::{c_char, CStr, CString}; 16 | 17 | use ash::vk::{self}; 18 | 19 | use mockall::automock; 20 | use vulkan_layer_macros::declare_introspection_queries; 21 | 22 | #[test] 23 | fn test_declare_introspection_queries_should_just_forward_to_global() { 24 | #[automock] 25 | trait Global { 26 | unsafe extern "system" fn enumerate_instance_layer_properties( 27 | property_count: *mut u32, 28 | properties: *mut vk::LayerProperties, 29 | ) -> vk::Result; 30 | unsafe extern "system" fn enumerate_instance_extension_properties( 31 | layer_name: *const c_char, 32 | property_count: *mut u32, 33 | extension_properties: *mut vk::ExtensionProperties, 34 | ) -> vk::Result; 35 | unsafe extern "system" fn enumerate_device_layer_properties( 36 | physical_device: vk::PhysicalDevice, 37 | p_property_count: *mut u32, 38 | p_properties: *mut vk::LayerProperties, 39 | ) -> vk::Result; 40 | unsafe extern "system" fn enumerate_device_extension_properties( 41 | physical_device: vk::PhysicalDevice, 42 | p_layer_name: *const c_char, 43 | p_property_count: *mut u32, 44 | p_properties: *mut vk::ExtensionProperties, 45 | ) -> vk::Result; 46 | unsafe extern "system" fn get_instance_proc_addr( 47 | instance: vk::Instance, 48 | p_name: *const c_char, 49 | ) -> vk::PFN_vkVoidFunction; 50 | unsafe extern "system" fn get_device_proc_addr( 51 | device: vk::Device, 52 | p_name: *const c_char, 53 | ) -> vk::PFN_vkVoidFunction; 54 | } 55 | 56 | declare_introspection_queries!(MockGlobal); 57 | 58 | { 59 | const COUNT: usize = 3; 60 | let mut layer_properties: [vk::LayerProperties; COUNT] = Default::default(); 61 | let mut out_count: u32 = COUNT as _; 62 | let return_val = vk::Result::ERROR_UNKNOWN; 63 | 64 | let ctx = MockGlobal::enumerate_instance_layer_properties_context(); 65 | // Raw pointers are not Send, so we need to use `withf_st`. 66 | ctx.expect() 67 | .once() 68 | .withf_st({ 69 | let expect_p_count = &mut out_count as *mut _; 70 | let expect_p_layer_properties = layer_properties.as_mut_ptr(); 71 | move |p_count, p_layer_properties| { 72 | (*p_count, *p_layer_properties) == (expect_p_count, expect_p_layer_properties) 73 | } 74 | }) 75 | .return_const(return_val); 76 | 77 | assert_eq!( 78 | unsafe { 79 | vkEnumerateInstanceLayerProperties(&mut out_count, layer_properties.as_mut_ptr()) 80 | }, 81 | return_val 82 | ); 83 | } 84 | { 85 | let layer_name = CString::new("VK_LAYER_UNKNOWN_test").unwrap(); 86 | const COUNT: usize = 3; 87 | let mut property_count = COUNT as _; 88 | let mut extension_properties: [vk::ExtensionProperties; COUNT] = Default::default(); 89 | let return_val = vk::Result::ERROR_UNKNOWN; 90 | 91 | let ctx = MockGlobal::enumerate_instance_extension_properties_context(); 92 | ctx.expect() 93 | .once() 94 | .withf_st({ 95 | let expect_layer_name = layer_name.as_ptr() as *const c_char; 96 | let expect_p_property_count = &mut property_count as *mut _; 97 | let expect_p_extension_properties = &mut extension_properties as *mut _; 98 | move |layer_name, property_count, extension_properties| { 99 | (*layer_name, *property_count, *extension_properties) 100 | == ( 101 | expect_layer_name, 102 | expect_p_property_count, 103 | expect_p_extension_properties, 104 | ) 105 | } 106 | }) 107 | .return_const(return_val); 108 | 109 | assert_eq!( 110 | unsafe { 111 | vkEnumerateInstanceExtensionProperties( 112 | layer_name.as_ptr(), 113 | &mut property_count, 114 | extension_properties.as_mut_ptr(), 115 | ) 116 | }, 117 | return_val 118 | ); 119 | } 120 | { 121 | // We use transmute instead of Handle::from_raw here to avoid integer to pointer cast, and 122 | // allow the miri tests with tree borrows to work with this test. See 123 | // https://github.com/ash-rs/ash/issues/996 for details. 124 | let physical_device = 125 | unsafe { std::mem::transmute::<*const u8, vk::PhysicalDevice>(std::ptr::dangling()) }; 126 | const COUNT: usize = 3; 127 | let mut count = COUNT as u32; 128 | let mut layer_properties: [vk::LayerProperties; COUNT] = Default::default(); 129 | let return_val = vk::Result::ERROR_UNKNOWN; 130 | 131 | let ctx = MockGlobal::enumerate_device_layer_properties_context(); 132 | ctx.expect() 133 | .once() 134 | .withf_st({ 135 | let expect_physical_device = physical_device; 136 | let expect_p_count = &mut count as *mut _; 137 | let expect_p_layer_properties = &mut layer_properties as *mut _; 138 | move |physical_device, p_property_count, p_properties| { 139 | (*physical_device, *p_property_count, *p_properties) 140 | == ( 141 | expect_physical_device, 142 | expect_p_count, 143 | expect_p_layer_properties, 144 | ) 145 | } 146 | }) 147 | .return_const(return_val); 148 | 149 | assert_eq!( 150 | unsafe { 151 | vkEnumerateDeviceLayerProperties( 152 | physical_device, 153 | &mut count, 154 | layer_properties.as_mut_ptr(), 155 | ) 156 | }, 157 | return_val 158 | ); 159 | } 160 | { 161 | // We use transmute instead of Handle::from_raw here to avoid integer to pointer cast, and 162 | // allow the miri tests with tree borrows to work with this test. See 163 | // https://github.com/ash-rs/ash/issues/996 for details. 164 | let instance = 165 | unsafe { std::mem::transmute::<*const u8, vk::Instance>(std::ptr::dangling()) }; 166 | let name = CString::new("vkCreateInstance").unwrap(); 167 | extern "system" fn fake_create_instance( 168 | _: *const vk::InstanceCreateInfo, 169 | _: *const vk::AllocationCallbacks, 170 | _: *mut vk::Instance, 171 | ) -> vk::Result { 172 | unimplemented!(); 173 | } 174 | let fake_create_instance_fp: vk::PFN_vkVoidFunction = 175 | unsafe { std::mem::transmute(fake_create_instance as vk::PFN_vkCreateInstance) }; 176 | 177 | let ctx = MockGlobal::get_instance_proc_addr_context(); 178 | ctx.expect() 179 | .once() 180 | .withf_st({ 181 | let expect_instance = instance; 182 | let expect_name = name.clone(); 183 | move |instance, name| { 184 | let name = unsafe { CStr::from_ptr(*name) }; 185 | (*instance, name) == (expect_instance, expect_name.as_c_str()) 186 | } 187 | }) 188 | .return_const(fake_create_instance_fp); 189 | 190 | assert_eq!( 191 | unsafe { vkGetInstanceProcAddr(instance, name.as_ptr()) }, 192 | fake_create_instance_fp 193 | ); 194 | } 195 | { 196 | // We use transmute instead of Handle::from_raw here to avoid integer to pointer cast, and 197 | // allow the miri tests with tree borrows to work with this test. See 198 | // https://github.com/ash-rs/ash/issues/996 for details. 199 | let device = unsafe { std::mem::transmute::<*const u8, vk::Device>(std::ptr::dangling()) }; 200 | let name = CString::new("vkAllocateMemory").unwrap(); 201 | extern "system" fn fake_allocate_memory( 202 | _: vk::Device, 203 | _: *const vk::MemoryAllocateInfo, 204 | _: *const vk::AllocationCallbacks, 205 | _: *mut vk::DeviceMemory, 206 | ) -> vk::Result { 207 | unimplemented!() 208 | } 209 | let fake_allocate_memory_fp: vk::PFN_vkVoidFunction = 210 | unsafe { std::mem::transmute(fake_allocate_memory as vk::PFN_vkAllocateMemory) }; 211 | 212 | let ctx = MockGlobal::get_device_proc_addr_context(); 213 | ctx.expect() 214 | .once() 215 | .withf_st({ 216 | let expect_device = device; 217 | let expect_name = name.clone(); 218 | move |device, name| { 219 | let name = unsafe { CStr::from_ptr(*name) }; 220 | (*device, name) == (expect_device, expect_name.as_c_str()) 221 | } 222 | }) 223 | .return_const(fake_allocate_memory_fp); 224 | 225 | assert_eq!( 226 | unsafe { vkGetDeviceProcAddr(device, name.as_ptr()) }, 227 | fake_allocate_memory_fp 228 | ); 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /yamlfmt.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | doublestar: true 15 | gitignore_excludes: true 16 | exclude: 17 | - "**/third_party/**" 18 | - "**/.git/**" 19 | --------------------------------------------------------------------------------